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