]> git.openstreetmap.org Git - rails.git/blob - public/javascripts/notes.js
2dc756bdc0f29c2d715c76e980cae11c9dd612b7
[rails.git] / public / javascripts / notes.js
1 /*
2         Dervied from the OpenStreetBugs client, which is available
3         under the following license.
4
5         This OpenStreetBugs client is free software: you can redistribute it
6         and/or modify it under the terms of the GNU Affero General Public License
7         as published by the Free Software Foundation, either version 3 of the
8         License, or (at your option) any later version.
9
10         This file is distributed in the hope that it will be useful, but
11         WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12         or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
13         License <http://www.gnu.org/licenses/> for more details.
14 */
15
16 OpenLayers.Layer.Notes = new OpenLayers.Class(OpenLayers.Layer.Markers, {
17     /**
18      * The URL of the OpenStreetMap API.
19      *
20      * @var String
21      */
22     serverURL : "/api/0.6/",
23
24     /**
25      * Associative array (index: note ID) that is filled with the notes
26      * loaded in this layer.
27      *
28      * @var String
29      */
30     notes : { },
31
32     /**
33      * The username to be used to change or create notes on OpenStreetMap.
34      *
35      * @var String
36      */
37     username : "NoName",
38
39     /**
40      * The icon to be used for an open note.
41      *
42      * @var OpenLayers.Icon
43      */
44     iconOpen : new OpenLayers.Icon("/images/open_note_marker.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
45
46     /**
47      * The icon to be used for a closed note.
48      *
49      * @var OpenLayers.Icon
50      */
51     iconClosed : new OpenLayers.Icon("/images/closed_note_marker.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
52
53     /**
54      * The projection of the coordinates sent by the OpenStreetMap API.
55      *
56      * @var OpenLayers.Projection
57      */
58     apiProjection : new OpenLayers.Projection("EPSG:4326"),
59
60     /**
61      * If this is set to true, the user may not commit comments or close notes.
62      *
63      * @var Boolean
64      */
65     readonly : false,
66
67     /**
68      * When the layer is hidden, all open popups are stored in this
69      * array in order to be re-opened again when the layer is made
70      * visible again.
71      */
72     reopenPopups : [ ],
73
74     /**
75      * A URL to append lon=123&lat=123&zoom=123 for the Permalinks.
76      *
77      * @var String
78      */
79     permalinkURL : "http://www.openstreetmap.org/",
80
81     /**
82      * A CSS file to be included. Set to null if you don’t need this.
83      *
84      * @var String
85      */
86     theme : "/stylesheets/notes.css",
87
88     /**
89      * @param String name
90      */
91     initialize: function(name, options) {
92         OpenLayers.Layer.Markers.prototype.initialize.apply(this, [
93             name,
94             OpenLayers.Util.extend({
95                 opacity: 0.7,
96                 projection: new OpenLayers.Projection("EPSG:4326") }, options)
97         ]);
98
99         putAJAXMarker.layers.push(this);
100         this.events.addEventType("markerAdded");
101
102         this.events.register("visibilitychanged", this, this.updatePopupVisibility);
103         this.events.register("visibilitychanged", this, this.loadNotes);
104
105         if (this.theme) {
106             // check existing links for equivalent url
107             var addNode = true;
108             var nodes = document.getElementsByTagName('link');
109             for (var i = 0, len = nodes.length; i < len; ++i) {
110                 if (OpenLayers.Util.isEquivalentUrl(nodes.item(i).href, this.theme)) {
111                     addNode = false;
112                     break;
113                 }
114             }
115             // only add a new node if one with an equivalent url hasn't already
116             // been added
117             if (addNode) {
118                 var cssNode = document.createElement('link');
119                 cssNode.setAttribute('rel', 'stylesheet');
120                 cssNode.setAttribute('type', 'text/css');
121                 cssNode.setAttribute('href', this.theme);
122                 document.getElementsByTagName('head')[0].appendChild(cssNode);
123             }
124         }
125     },
126
127     /**
128      * Called automatically called when the layer is added to a map.
129      * Initialises the automatic note loading in the visible bounding box.
130      */
131     afterAdd: function() {
132         var ret = OpenLayers.Layer.Markers.prototype.afterAdd.apply(this, arguments);
133
134         this.map.events.register("moveend", this, this.loadNotes);
135         this.loadNotes();
136
137         return ret;
138     },
139
140     /**
141      * At the moment the OpenStreetMap API responses to requests using
142      * JavaScript code. This way the Same Origin Policy can be worked
143      * around. Unfortunately, this makes communicating with the API a
144      * bit too asynchronous, at the moment there is no way to tell to
145      * which request the API actually responses.
146      *
147      * This method creates a new script HTML element that imports the
148      * API request URL. The API JavaScript response then executes the
149      * global functions provided below.
150      *
151      * @param String url The URL this.serverURL + url is requested.
152      */
153     apiRequest: function(url) {
154         var script = document.createElement("script");
155         script.type = "text/javascript";
156         script.src = this.serverURL + url + "&nocache="+(new Date()).getTime();
157         document.body.appendChild(script);
158     },
159
160     /**
161      * Is automatically called when the visibility of the layer
162      * changes. When the layer is hidden, all visible popups are
163      * closed and their visibility is saved. When the layer is made
164      * visible again, these popups are re-opened.
165      */
166     updatePopupVisibility: function() {
167         if (this.getVisibility()) {
168             for (var i =0 ; i < this.reopenPopups.length; i++)
169                 this.reopenPopups[i].show();
170
171             this.reopenPopups = [ ];
172         } else {
173             for (var i = 0; i < this.markers.length; i++) {
174                 if (this.markers[i].feature.popup &&
175                     this.markers[i].feature.popup.visible()) {
176                     this.markers[i].feature.popup.hide();
177                     this.reopenPopups.push(this.markers[i].feature.popup);
178                 }
179             }
180         }
181     },
182
183     /**
184      * Sets the user name to be used for interactions with OpenStreetMap.
185      */
186     setUserName: function(username) {
187         if (this.username == username)
188             return;
189
190         this.username = username;
191
192         for (var i = 0; i < this.markers.length; i++) {
193             var popup = this.markers[i].feature.popup;
194
195             if (popup) {
196                 var els = popup.contentDom.getElementsByTagName("input");
197
198                 for (var j = 0; j < els.length; j++) {
199                     if (els[j].className == "username")
200                         els[j].value = username;
201                 }
202             }
203         }
204     },
205
206     /**
207      * Returns the currently set username or “NoName” if none is set.
208      */
209     getUserName: function() {
210         if(this.username)
211             return this.username;
212         else
213             return "NoName";
214     },
215
216     /**
217      * Loads the notes in the current bounding box. Is automatically
218      * called by an event handler ("moveend" event) that is created in
219      * the afterAdd() method.
220      */
221     loadNotes: function() {
222         var bounds = this.map.getExtent();
223
224         if (bounds && this.getVisibility()) {
225             bounds.transform(this.map.getProjectionObject(), this.apiProjection);
226
227             this.apiRequest("notes"
228                             + "?bbox=" + this.round(bounds.left, 5)
229                             + "," + this.round(bounds.bottom, 5)
230                             + "," + this.round(bounds.right, 5)
231                             + "," + this.round(bounds.top, 5));
232         }
233     },
234
235     /**
236      * Rounds the given number to the given number of digits after the
237      * floating point.
238      *
239      * @param Number number
240      * @param Number digits
241      * @return Number
242      */
243     round: function(number, digits) {
244         var scale = Math.pow(10, digits);
245
246         return Math.round(number * scale) / scale;
247     },
248
249     /**
250      * Adds an OpenLayers.Marker representing a note to the map. Is
251      * usually called by loadNotes().
252      *
253      * @param Number id The note ID
254      */
255     createMarker: function(id) {
256         if (this.notes[id]) {
257             if (this.notes[id].popup && !this.notes[id].popup.visible())
258                 this.setPopupContent(this.notes[id].popup, id);
259
260             if (this.notes[id].closed != putAJAXMarker.notes[id][2])
261                 this.notes[id].destroy();
262             else
263                 return;
264         }
265
266         var lonlat = putAJAXMarker.notes[id][0].clone().transform(this.apiProjection, this.map.getProjectionObject());
267         var comments = putAJAXMarker.notes[id][1];
268         var closed = putAJAXMarker.notes[id][2];
269         var icon = closed ? this.iconClosed : this.iconOpen;
270
271         var feature = new OpenLayers.Feature(this, lonlat, {
272             icon: icon.clone(),
273             autoSize: true
274         });
275         feature.popupClass = OpenLayers.Popup.FramedCloud.Notes;
276         feature.noteId = id;
277         feature.closed = closed;
278         this.notes[id] = feature;
279
280         var marker = feature.createMarker();
281         marker.feature = feature;
282         marker.events.register("click", feature, this.markerClick);
283         //marker.events.register("mouseover", feature, this.markerMouseOver);
284         //marker.events.register("mouseout", feature, this.markerMouseOut);
285         this.addMarker(marker);
286
287         this.events.triggerEvent("markerAdded");
288     },
289
290     /**
291      * Recreates the content of the popup of a marker.
292      *
293      * @param OpenLayers.Popup popup
294      * @param Number id The note ID
295      */
296     setPopupContent: function(popup, id) {
297         var el1,el2,el3;
298         var layer = this;
299
300         var newContent = document.createElement("div");
301
302         el1 = document.createElement("h3");
303         el1.appendChild(document.createTextNode(putAJAXMarker.notes[id][2] ? i18n("javascripts.note.closed") : i18n("javascripts.note.open")));
304
305         el1.appendChild(document.createTextNode(" ["));
306         el2 = document.createElement("a");
307         el2.href = "/browse/note/" + id;
308         el2.onclick = function() {
309             layer.map.setCenter(putAJAXMarker.notes[id][0].clone().transform(layer.apiProjection, layer.map.getProjectionObject()), 15);
310         };
311         el2.appendChild(document.createTextNode(i18n("javascripts.note.details")));
312         el1.appendChild(el2);
313         el1.appendChild(document.createTextNode("]"));
314
315         if (this.permalinkURL) {
316             el1.appendChild(document.createTextNode(" ["));
317             el2 = document.createElement("a");
318             el2.href = this.permalinkURL + (this.permalinkURL.indexOf("?") == -1 ? "?" : "&") + "lon="+putAJAXMarker.notes[id][0].lon+"&lat="+putAJAXMarker.notes[id][0].lat+"&zoom=15";
319             el2.appendChild(document.createTextNode(i18n("javascripts.note.permalink")));
320             el1.appendChild(el2);
321             el1.appendChild(document.createTextNode("]"));
322         }
323         newContent.appendChild(el1);
324
325         var containerDescription = document.createElement("div");
326         newContent.appendChild(containerDescription);
327
328         var containerChange = document.createElement("div");
329         newContent.appendChild(containerChange);
330
331         var displayDescription = function() {
332             containerDescription.style.display = "block";
333             containerChange.style.display = "none";
334             popup.updateSize();
335         };
336         var displayChange = function() {
337             containerDescription.style.display = "none";
338             containerChange.style.display = "block";
339             popup.updateSize();
340         };
341         displayDescription();
342
343         el1 = document.createElement("dl");
344         for (var i = 0; i < putAJAXMarker.notes[id][1].length; i++) {
345             el2 = document.createElement("dt");
346             el2.className = (i == 0 ? "note-description" : "note-comment");
347             el2.appendChild(document.createTextNode(i == 0 ? i18n("javascripts.note.description") : i18n("javascripts.note.comment")));
348             el1.appendChild(el2);
349             el2 = document.createElement("dd");
350             el2.className = (i == 0 ? "note-description" : "note-comment");
351             el2.appendChild(document.createTextNode(putAJAXMarker.notes[id][1][i]));
352             el1.appendChild(el2);
353             if (i == 0) {
354                 el2 = document.createElement("br");
355                 el1.appendChild(el2);
356             };
357         }
358         containerDescription.appendChild(el1);
359
360         if (putAJAXMarker.notes[id][2]) {
361             el1 = document.createElement("p");
362             el1.className = "note-fixed";
363             el2 = document.createElement("em");
364             el2.appendChild(document.createTextNode(i18n("javascripts.note.render_warning")));
365             el1.appendChild(el2);
366             containerDescription.appendChild(el1);
367         } else if (!this.readonly) {
368             el1 = document.createElement("div");
369             el2 = document.createElement("input");
370             el2.setAttribute("type", "button");
371             el2.onclick = function() {
372                 displayChange();
373             };
374             el2.value = i18n("javascripts.note.update");
375             el1.appendChild(el2);
376             containerDescription.appendChild(el1);
377
378             var el_form = document.createElement("form");
379             el_form.onsubmit = function() {
380                 if (inputComment.value.match(/^\s*$/))
381                     return false;
382                 layer.submitComment(id, inputComment.value);
383                 layer.hidePopup(popup);
384                 return false;
385             };
386
387             el1 = document.createElement("dl");
388             el2 = document.createElement("dt");
389             el2.appendChild(document.createTextNode(i18n("javascripts.note.nickname")));
390             el1.appendChild(el2);
391             el2 = document.createElement("dd");
392             var inputUsername = document.createElement("input");
393             var inputUsername = document.createElement("input");;
394             if (typeof loginName === "undefined") {
395                 inputUsername.value = this.username;
396             } else {
397                 inputUsername.value = loginName;
398                 inputUsername.setAttribute("disabled", "true");
399             }
400             inputUsername.className = "username";
401             inputUsername.onkeyup = function() {
402                 layer.setUserName(inputUsername.value);
403             };
404             el2.appendChild(inputUsername);
405             el3 = document.createElement("a");
406             el3.setAttribute("href", "login");
407             el3.className = "hide_if_logged_in";
408             el3.appendChild(document.createTextNode(i18n("javascripts.note.login")));
409             el2.appendChild(el3)
410             el1.appendChild(el2);
411
412             el2 = document.createElement("dt");
413             el2.appendChild(document.createTextNode(i18n("javascripts.note.comment")));
414             el1.appendChild(el2);
415             el2 = document.createElement("dd");
416             var inputComment = document.createElement("textarea");
417             inputComment.setAttribute("cols",40);
418             inputComment.setAttribute("rows",3);
419
420             el2.appendChild(inputComment);
421             el1.appendChild(el2);
422
423             el_form.appendChild(el1);
424
425             el1 = document.createElement("ul");
426             el1.className = "buttons";
427             el2 = document.createElement("li");
428             el3 = document.createElement("input");
429             el3.setAttribute("type", "button");
430             el3.onclick = function() {
431                 this.form.onsubmit();
432                 return false;
433             };
434             el3.value = i18n("javascripts.note.add_comment");
435             el2.appendChild(el3);
436             el1.appendChild(el2);
437
438             el2 = document.createElement("li");
439             el3 = document.createElement("input");
440             el3.setAttribute("type", "button");
441             el3.onclick = function() {
442                 this.form.onsubmit();
443                 layer.closeNote(id);
444                 popup.hide();
445                 return false;
446             };
447             el3.value = i18n("javascripts.note.close");
448             el2.appendChild(el3);
449             el1.appendChild(el2);
450             el_form.appendChild(el1);
451             containerChange.appendChild(el_form);
452
453             el1 = document.createElement("div");
454             el2 = document.createElement("input");
455             el2.setAttribute("type", "button");
456             el2.onclick = function(){ displayDescription(); };
457             el2.value = i18n("javascripts.note.cancel");
458             el1.appendChild(el2);
459             containerChange.appendChild(el1);
460         }
461
462         popup.setContentHTML(newContent);
463     },
464
465     /**
466      * Creates a new note.
467      *
468      * @param OpenLayers.LonLat lonlat The coordinates in the API projection.
469      * @param String description
470      */
471     createNote: function(lonlat, description) {
472         this.apiRequest("note/create"
473                         + "?lat=" + encodeURIComponent(lonlat.lat)
474                         + "&lon=" + encodeURIComponent(lonlat.lon)
475                         + "&text=" + encodeURIComponent(description)
476                         + "&name=" + encodeURIComponent(this.getUserName())
477                         + "&format=js");
478     },
479
480     /**
481      * Adds a comment to a note.
482      *
483      * @param Number id
484      * @param String comment
485      */
486     submitComment: function(id, comment) {
487         this.apiRequest("note/" + encodeURIComponent(id) + "/comment"
488                         + "?text=" + encodeURIComponent(comment)
489                         + "&name=" + encodeURIComponent(this.getUserName())
490                         + "&format=js");
491     },
492
493     /**
494      * Marks a note as fixed.
495      *
496      * @param Number id
497      */
498     closeNote: function(id) {
499         this.apiRequest("note/" + encodeURIComponent(id) + "/close"
500                         + "?format=js");
501     },
502
503     /**
504      * Removes the content of a marker popup (to reduce the amount of
505      * needed resources).
506      *
507      * @param OpenLayers.Popup popup
508      */
509     resetPopupContent: function(popup) {
510         if (popup)
511             popup.setContentHTML(document.createElement("div"));
512     },
513
514     /**
515      * Makes the popup of the given marker visible. Makes sure that
516      * the popup content is created if it does not exist yet.
517      *
518      * @param OpenLayers.Feature feature
519      */
520     showPopup: function(feature) {
521         var popup = feature.popup;
522
523         if (!popup) {
524             popup = feature.createPopup(true);
525
526             popup.events.register("close", this, function() {
527                 this.resetPopupContent(popup);
528             });
529         }
530
531         this.setPopupContent(popup, feature.noteId);
532
533         if (!popup.map)
534             this.map.addPopup(popup);
535
536         popup.updateSize();
537
538         if (!popup.visible())
539             popup.show();
540     },
541
542     /**
543      * Hides the popup of the given marker.
544      *
545      * @param OpenLayers.Feature feature
546      */
547     hidePopup: function(feature) {
548         if (feature.popup && feature.popup.visible()) {
549             feature.popup.hide();
550             feature.popup.events.triggerEvent("close");
551         }
552     },
553
554     /**
555      * Is run on the “click” event of a marker in the context of its
556      * OpenLayers.Feature. Toggles the visibility of the popup.
557      */
558     markerClick: function(e) {
559         var feature = this;
560
561         if (feature.popup && feature.popup.visible())
562             feature.layer.hidePopup(feature);
563         else
564             feature.layer.showPopup(feature);
565
566         OpenLayers.Event.stop(e);
567     },
568
569     /**
570      * Is run on the “mouseover” event of a marker in the context of
571      * its OpenLayers.Feature. Makes the popup visible.
572      */
573     markerMouseOver: function(e) {
574         var feature = this;
575
576         feature.layer.showPopup(feature);
577
578         OpenLayers.Event.stop(e);
579     },
580
581     /**
582      * Is run on the “mouseout” event of a marker in the context of
583      * its OpenLayers.Feature. Hides the popup (if it has not been
584      * clicked).
585      */
586     markerMouseOut: function(e) {
587         var feature = this;
588
589         if (feature.popup && feature.popup.visible())
590             feature.layer.hidePopup(feature);
591
592         OpenLayers.Event.stop(e);
593     },
594
595     CLASS_NAME: "OpenLayers.Layer.Notes"
596 });
597
598 /**
599  * An OpenLayers control to create new notes on mouse clicks on the
600  * map. Add an instance of this to your map using the
601  * OpenLayers.Map.addControl() method and activate() it.
602  */
603 OpenLayers.Control.Notes = new OpenLayers.Class(OpenLayers.Control, {
604     title : null, // See below because of translation call
605
606     /**
607      * The icon to be used for the temporary markers that the “create
608      * note” popup belongs to..
609      *
610      * @var OpenLayers.Icon
611      */
612     icon : new OpenLayers.Icon("/images/icon_note_add.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
613
614     /**
615      * An instance of the Notes layer that this control shall be
616      * connected to. Is set in the constructor.
617      *
618      * @var OpenLayers.Layer.Notes
619      */
620     noteLayer : null,
621
622     /**
623      * @param OpenLayers.Layer.Notes noteLayer The Notes layer that this control will be connected to.
624      */
625     initialize: function(noteLayer, options) {
626         this.noteLayer = noteLayer;
627
628         this.title = i18n("javascripts.note.create");
629
630         OpenLayers.Control.prototype.initialize.apply(this, [ options ]);
631
632         this.events.register("activate", this, function() {
633             if(!this.noteLayer.getVisibility())
634                 this.noteLayer.setVisibility(true);
635         });
636
637         this.noteLayer.events.register("visibilitychanged", this, function() {
638             if(this.active && !this.noteLayer.getVisibility())
639                 this.noteLayer.setVisibility(true);
640         });
641     },
642
643     destroy: function() {
644         if (this.handler)
645             this.handler.destroy();
646         this.handler = null;
647
648         OpenLayers.Control.prototype.destroy.apply(this, arguments);
649     },
650
651     draw: function() {
652         this.handler = new OpenLayers.Handler.Click(this, {'click': this.click}, { 'single': true, 'double': false, 'pixelTolerance': 0, 'stopSingle': false, 'stopDouble': false });
653     },
654
655     /**
656      * Map clicking event handler. Adds a temporary marker with a
657      * popup to the map, the popup contains the form to add a note.
658      */
659     click: function(e) {
660         var lonlat = this.map.getLonLatFromViewPortPx(e.xy);
661         this.addTemporaryMarker(lonlat);
662     },
663
664     addTemporaryMarker: function(lonlat) {
665         if(!this.map) return true;
666
667         var control = this;
668         var map = control.map;
669         var lonlatApi = lonlat.clone().transform(map.getProjectionObject(), this.noteLayer.apiProjection);
670         var feature = new OpenLayers.Feature(this.noteLayer, lonlat, { icon: this.icon.clone(), autoSize: true });
671         feature.popupClass = OpenLayers.Popup.FramedCloud.Notes;
672         var marker = feature.createMarker();
673         marker.feature = feature;
674         this.noteLayer.addMarker(marker);
675
676
677         /** Implement a drag and drop for markers */
678         /* TODO: veryfy that the scoping of variables works correctly everywhere */
679         var dragging = false;
680         var dragMove = function(e) {
681             lonlat = map.getLonLatFromViewPortPx(e.xy);
682             lonlatApi = lonlat.clone().transform(map.getProjectionObject(), map.noteLayer.apiProjection);
683             marker.moveTo(map.getLayerPxFromViewPortPx(e.xy));
684             marker.popup.moveTo(map.getLayerPxFromViewPortPx(e.xy));
685             marker.popup.updateRelativePosition();
686             return false;
687         };
688         var dragComplete = function(e) {
689             map.events.unregister("mousemove", map, dragMove);
690             map.events.unregister("mouseup", map, dragComplete);
691             dragMove(e);
692             dragging = false;
693             return false;
694         };
695
696         marker.events.register("mouseover", this, function() {
697             map.viewPortDiv.style.cursor = "move";
698         });
699         marker.events.register("mouseout", this, function() {
700             if (!dragging)
701                 map.viewPortDiv.style.cursor = "default";
702         });
703         marker.events.register("mousedown", this, function() {
704             dragging = true;
705             map.events.register("mousemove", map, dragMove);
706             map.events.register("mouseup", map, dragComplete);
707             return false;
708         });
709
710         var newContent = document.createElement("div");
711         var el1,el2,el3;
712         el1 = document.createElement("h3");
713         el1.appendChild(document.createTextNode(i18n("javascripts.note.create_title")));
714         newContent.appendChild(el1);
715         newContent.appendChild(document.createTextNode(i18n("javascripts.note.create_help1")));
716         newContent.appendChild(document.createElement("br"));
717         newContent.appendChild(document.createTextNode(i18n("javascripts.note.create_help2")));
718         newContent.appendChild(document.createElement("br"));
719         newContent.appendChild(document.createElement("br"));
720
721         var el_form = document.createElement("form");
722
723         el1 = document.createElement("dl");
724         el2 = document.createElement("dt");
725         el2.appendChild(document.createTextNode(i18n("javascripts.note.nickname")));
726         el1.appendChild(el2);
727         el2 = document.createElement("dd");
728         var inputUsername = document.createElement("input");;
729         if (typeof loginName === 'undefined') {
730             inputUsername.value = this.noteLayer.username;
731         } else {
732             inputUsername.value = loginName;
733             inputUsername.setAttribute('disabled','true');
734         }
735         inputUsername.className = "username";
736
737         inputUsername.onkeyup = function(){ control.noteLayer.setUserName(inputUsername.value); };
738         el2.appendChild(inputUsername);
739         el3 = document.createElement("a");
740         el3.setAttribute("href","login");
741         el3.className = "hide_if_logged_in";
742         el3.appendChild(document.createTextNode(i18n("javascripts.note.login")));
743         el2.appendChild(el3);
744         el1.appendChild(el2);
745         el2 = document.createElement("br");
746         el1.appendChild(el2);
747
748         el2 = document.createElement("dt");
749         el2.appendChild(document.createTextNode(i18n("javascripts.note.description")));
750         el1.appendChild(el2);
751         el2 = document.createElement("dd");
752         var inputDescription = document.createElement("textarea");
753         inputDescription.setAttribute("cols",40);
754         inputDescription.setAttribute("rows",3);
755         el2.appendChild(inputDescription);
756         el1.appendChild(el2);
757         el_form.appendChild(el1);
758
759         el1 = document.createElement("div");
760         el2 = document.createElement("input");
761         el2.setAttribute("type", "button");
762         el2.value = i18n("javascripts.note.report");
763         el2.onclick = function() { control.noteLayer.createNote(lonlatApi, inputDescription.value); marker.feature = null; feature.destroy(); return false; };
764         el1.appendChild(el2);
765         el2 = document.createElement("input");
766         el2.setAttribute("type", "button");
767         el2.value = i18n("javascripts.note.cancel");
768         el2.onclick = function(){ feature.destroy(); };
769         el1.appendChild(el2);
770         el_form.appendChild(el1);
771         newContent.appendChild(el_form);
772
773         el2 = document.createElement("hr");
774         el1.appendChild(el2);
775         el2 = document.createElement("a");
776         el2.setAttribute("href","edit");
777         el2.appendChild(document.createTextNode(i18n("javascripts.note.edityourself")));
778         el1.appendChild(el2);
779
780         feature.data.popupContentHTML = newContent;
781         var popup = feature.createPopup(true);
782         popup.events.register("close", this, function(){ feature.destroy(); });
783         map.addPopup(popup);
784         popup.updateSize();
785         marker.popup = popup;
786     },
787
788     CLASS_NAME: "OpenLayers.Control.Notes"
789 });
790
791
792 /**
793  * This class changes the usual OpenLayers.Popup.FramedCloud class by
794  * using a DOM element instead of an innerHTML string as content for
795  * the popup.  This is necessary for creating valid onclick handlers
796  * that still work with multiple Notes layer objects.
797  */
798 OpenLayers.Popup.FramedCloud.Notes = new OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
799     contentDom : null,
800     autoSize : true,
801
802     /**
803      * See OpenLayers.Popup.FramedCloud.initialize() for
804      * parameters. As fourth parameter, pass a DOM node instead of a
805      * string.
806      */
807     initialize: function() {
808         this.displayClass = this.displayClass + " " + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
809
810         var args = new Array(arguments.length);
811         for(var i=0; i<arguments.length; i++)
812             args[i] = arguments[i];
813
814         // Unset original contentHTML parameter
815         args[3] = null;
816
817         var closeCallback = arguments[6];
818
819         // Add close event trigger to the closeBoxCallback parameter
820         args[6] = function(e){ if(closeCallback) closeCallback(); else this.hide(); OpenLayers.Event.stop(e); this.events.triggerEvent("close"); };
821
822         OpenLayers.Popup.FramedCloud.prototype.initialize.apply(this, args);
823
824         this.events.addEventType("close");
825
826         this.setContentHTML(arguments[3]);
827     },
828
829     /**
830      * Like OpenLayers.Popup.FramedCloud.setContentHTML(), but takes a
831      * DOM element as parameter.
832      */
833     setContentHTML: function(contentDom) {
834         if(contentDom != null)
835             this.contentDom = contentDom;
836
837         if(this.contentDiv == null || this.contentDom == null || this.contentDom == this.contentDiv.firstChild)
838             return;
839
840         while(this.contentDiv.firstChild)
841             this.contentDiv.removeChild(this.contentDiv.firstChild);
842
843         this.contentDiv.appendChild(this.contentDom);
844
845         // Copied from OpenLayers.Popup.setContentHTML():
846         if(this.autoSize)
847         {
848             this.registerImageListeners();
849             this.updateSize();
850         }
851     },
852
853     destroy: function() {
854         this.contentDom = null;
855         OpenLayers.Popup.FramedCloud.prototype.destroy.apply(this, arguments);
856     },
857
858     CLASS_NAME: "OpenLayers.Popup.FramedCloud.Notes"
859 });
860
861
862 /**
863  * This global function is executed by the OpenStreetMap API getBugs script.
864  *
865  * Each Notes layer adds itself to the putAJAXMarker.layer array. The
866  * putAJAXMarker() function executes the createMarker() method on each
867  * layer in that array each time it is called. This has the
868  * side-effect that notes displayed in one map on a page are already
869  * loaded on the other map as well.
870  */
871 function putAJAXMarker(id, lon, lat, text, closed)
872 {
873     var comments = text.split(/<hr \/>/);
874     for(var i=0; i<comments.length; i++)
875         comments[i] = comments[i].replace(/&quot;/g, "\"").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
876     putAJAXMarker.notes[id] = [
877         new OpenLayers.LonLat(lon, lat),
878         comments,
879         closed
880     ];
881     for(var i=0; i<putAJAXMarker.layers.length; i++)
882         putAJAXMarker.layers[i].createMarker(id);
883 }
884
885 /**
886  * This global function is executed by the OpenStreetMap API. The
887  * “create note”, “comment” and “close note” scripts execute it to give
888  * information about their success.
889  *
890  * In case of success, this function is called without a parameter, in
891  * case of an error, the error message is passed. This is lousy
892  * workaround to make it any functional at all, the OSB API is likely
893  * to be extended later (then it will provide additional information
894  * such as the ID of a created note and similar).
895  */
896 function osbResponse(error)
897 {
898     if(error)
899         alert("Error: "+error);
900
901     for(var i=0; i<putAJAXMarker.layers.length; i++)
902         putAJAXMarker.layers[i].loadNotes();
903 }
904
905 putAJAXMarker.layers = [ ];
906 putAJAXMarker.notes = { };