4734985824d4278edb3724a085f5a1ed43a973d9
[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         deactivateControl();
693         
694         var control = this;
695         var lonlatApi = lonlat.clone().transform(this.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 dragFunction = function(e) {
707             map.events.unregister("mouseup",map,dragFunction);
708             lonlat = map.getLonLatFromViewPortPx(e.xy);
709             lonlatApi = lonlat.clone().transform(map.getProjectionObject(), map.noteLayer.apiProjection);
710             marker.moveTo(map.getLayerPxFromViewPortPx(e.xy));
711             marker.popup.moveTo(map.getLayerPxFromViewPortPx(e.xy));                    
712             marker.popup.updateRelativePosition();
713             dragging = false;
714             return false;
715         };
716         
717         marker.events.register("mouseover", this,
718                                function(){ document.getElementById("OpenLayers.Map_18_OpenLayers_Container").style.cursor = "move"; });
719         marker.events.register("mouseout", this,
720                                function(){ if (!dragging) {document.getElementById("OpenLayers.Map_18_OpenLayers_Container").style.cursor = "default"; }});
721         marker.events.register("mousedown", this,
722                                function() { dragging = true; map.events.register("mouseup",map, dragFunction); return false;});
723         
724         
725         var newContent = document.createElement("div");
726         var el1,el2,el3;
727         el1 = document.createElement("h3");
728         el1.appendChild(document.createTextNode(i18n("javascripts.note.create_title")));
729         newContent.appendChild(el1);
730         newContent.appendChild(document.createTextNode(i18n("javascripts.note.create_help1")));
731         newContent.appendChild(document.createElement("br"));
732         newContent.appendChild(document.createTextNode(i18n("javascripts.note.create_help2")));
733         newContent.appendChild(document.createElement("br"));
734         newContent.appendChild(document.createElement("br"));
735         
736         var el_form = document.createElement("form");
737         
738         el1 = document.createElement("dl");
739         el2 = document.createElement("dt");
740         el2.appendChild(document.createTextNode(i18n("javascripts.note.nickname")));
741         el1.appendChild(el2);
742         el2 = document.createElement("dd");
743         var inputUsername = document.createElement("input");;
744         if (typeof loginName === 'undefined') {
745             inputUsername.value = this.noteLayer.username;
746         } else {
747             inputUsername.value = loginName;
748             inputUsername.setAttribute('disabled','true');
749         }               
750         inputUsername.className = "osbUsername";
751         
752         inputUsername.onkeyup = function(){ control.noteLayer.setUserName(inputUsername.value); };
753         el2.appendChild(inputUsername);
754         el3 = document.createElement("a");
755         el3.setAttribute("href","login");
756         el3.className = "hide_if_logged_in";
757         el3.appendChild(document.createTextNode(i18n("javascripts.note.login")));
758         el2.appendChild(el3);
759         el1.appendChild(el2);
760         el2 = document.createElement("br");
761         el1.appendChild(el2);
762         
763         el2 = document.createElement("dt");
764         el2.appendChild(document.createTextNode(i18n("javascripts.note.description")));
765         el1.appendChild(el2);
766         el2 = document.createElement("dd");
767         var inputDescription = document.createElement("textarea");
768         inputDescription.setAttribute("cols",40);
769         inputDescription.setAttribute("rows",3);
770         el2.appendChild(inputDescription);
771         el1.appendChild(el2);
772         el_form.appendChild(el1);
773         
774         el1 = document.createElement("div");
775         el2 = document.createElement("input");
776         el2.setAttribute("type", "button");
777         el2.value = i18n("javascripts.note.report");
778         el2.onclick = function() { control.noteLayer.createNote(lonlatApi, inputDescription.value); marker.feature = null; feature.destroy(); return false; };
779         el1.appendChild(el2);
780         el2 = document.createElement("input");
781         el2.setAttribute("type", "button");
782         el2.value = i18n("javascripts.note.cancel");
783         el2.onclick = function(){ feature.destroy(); };
784         el1.appendChild(el2);
785         el_form.appendChild(el1);
786         newContent.appendChild(el_form);
787         
788         el2 = document.createElement("hr");
789         el1.appendChild(el2);
790         el2 = document.createElement("a");
791         el2.setAttribute("href","edit");
792         el2.appendChild(document.createTextNode(i18n("javascripts.note.edityourself")));
793         el1.appendChild(el2);
794         
795         feature.data.popupContentHTML = newContent;
796         var popup = feature.createPopup(true);
797         popup.events.register("close", this, function(){ feature.destroy(); });
798         this.map.addPopup(popup);
799         popup.updateSize();
800         marker.popup = popup;
801     },
802     
803     CLASS_NAME: "OpenLayers.Control.Notes"
804 });
805
806
807 /**
808  * This class changes the usual OpenLayers.Popup.FramedCloud class by
809  * using a DOM element instead of an innerHTML string as content for
810  * the popup.  This is necessary for creating valid onclick handlers
811  * that still work with multiple Notes layer objects.
812  */
813 OpenLayers.Popup.FramedCloud.Notes = new OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
814     contentDom : null,
815     autoSize : true,
816     
817     /**
818      * See OpenLayers.Popup.FramedCloud.initialize() for
819      * parameters. As fourth parameter, pass a DOM node instead of a
820      * string.
821      */
822     initialize: function() {
823         this.displayClass = this.displayClass + " " + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
824         
825         var args = new Array(arguments.length);
826         for(var i=0; i<arguments.length; i++)
827             args[i] = arguments[i];
828         
829         // Unset original contentHTML parameter
830         args[3] = null;
831         
832         var closeCallback = arguments[6];
833         
834         // Add close event trigger to the closeBoxCallback parameter
835         args[6] = function(e){ if(closeCallback) closeCallback(); else this.hide(); OpenLayers.Event.stop(e); this.events.triggerEvent("close"); };
836         
837         OpenLayers.Popup.FramedCloud.prototype.initialize.apply(this, args);
838         
839         this.events.addEventType("close");
840         
841         this.setContentHTML(arguments[3]);
842     },
843     
844     /**
845      * Like OpenLayers.Popup.FramedCloud.setContentHTML(), but takes a
846      * DOM element as parameter.
847      */
848     setContentHTML: function(contentDom) {
849         if(contentDom != null)
850             this.contentDom = contentDom;
851         
852         if(this.contentDiv == null || this.contentDom == null || this.contentDom == this.contentDiv.firstChild)
853             return;
854         
855         while(this.contentDiv.firstChild)
856             this.contentDiv.removeChild(this.contentDiv.firstChild);
857         
858         this.contentDiv.appendChild(this.contentDom);
859         
860         // Copied from OpenLayers.Popup.setContentHTML():
861         if(this.autoSize)
862         {
863             this.registerImageListeners();
864             this.updateSize();
865         }
866     },
867     
868     destroy: function() {
869         this.contentDom = null;
870         OpenLayers.Popup.FramedCloud.prototype.destroy.apply(this, arguments);
871     },
872     
873     CLASS_NAME: "OpenLayers.Popup.FramedCloud.Notes"
874 });
875
876
877 /**
878  * This global function is executed by the OpenStreetMap API getBugs script.
879  *
880  * Each Notes layer adds itself to the putAJAXMarker.layer array. The
881  * putAJAXMarker() function executes the createMarker() method on each
882  * layer in that array each time it is called. This has the
883  * side-effect that notes displayed in one map on a page are already
884  * loaded on the other map as well.
885  */
886 function putAJAXMarker(id, lon, lat, text, closed)
887 {
888     var comments = text.split(/<hr \/>/);
889     for(var i=0; i<comments.length; i++)
890         comments[i] = comments[i].replace(/&quot;/g, "\"").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
891     putAJAXMarker.notes[id] = [
892         new OpenLayers.LonLat(lon, lat),
893         comments,
894         closed
895     ];
896     for(var i=0; i<putAJAXMarker.layers.length; i++)
897         putAJAXMarker.layers[i].createMarker(id);
898 }
899
900 /**
901  * This global function is executed by the OpenStreetMap API. The
902  * “create note”, “comment” and “close note” scripts execute it to give
903  * information about their success.
904  *
905  * In case of success, this function is called without a parameter, in
906  * case of an error, the error message is passed. This is lousy
907  * workaround to make it any functional at all, the OSB API is likely
908  * to be extended later (then it will provide additional information
909  * such as the ID of a created note and similar).
910  */
911 function osbResponse(error)
912 {
913     if(error)
914         alert("Error: "+error);
915     
916     for(var i=0; i<putAJAXMarker.layers.length; i++)
917         putAJAXMarker.layers[i].loadNotes();
918 }
919
920 putAJAXMarker.layers = [ ];
921 putAJAXMarker.notes = { };
922
923 function deactivateControl() { 
924     map.noteControl.deactivate(); 
925     document.getElementById("OpenLayers.Map_18_OpenLayers_Container").style.cursor = "default"; 
926 }