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