a8aba9be711c2ba2c51684881df015805fb4d12a
[rails.git] / public / javascripts / openstreetbugs.js
1 /*
2         This OpenStreetBugs client is free software: you can redistribute it
3         and/or modify it under the terms of the GNU Affero General Public License
4         as published by the Free Software Foundation, either version 3 of the
5         License, or (at your option) any later version.
6
7         This file is distributed in the hope that it will be useful, but
8         WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9         or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
10         License <http://www.gnu.org/licenses/> for more details.
11 */
12
13 /**
14  * A fully functional OpenStreetBugs layer. See http://openstreetbugs.schokokeks.org/.
15  * Even though the OpenStreetBugs API originally does not intend this, you can create multiple instances of this Layer and add them to different maps (or to one single map for whatever crazy reason) without problems.
16 */
17
18 /** This version has been adapted from the original javascript library to fit the openstreetmap rails_port implementation */
19
20 OpenLayers.Layer.OpenStreetBugs = new OpenLayers.Class(OpenLayers.Layer.Markers, {
21         /**
22          * The URL of the OpenStreetBugs API.
23          * @var String
24         */
25         serverURL : "/api/0.6/",
26
27         /**
28          * Associative array (index: bug ID) that is filled with the bugs loaded in this layer
29          * @var String
30         */
31         bugs : { },
32
33         /**
34          * The username to be used to change or create bugs on OpenStreetBugs
35          * @var String
36         */
37         username : "NoName",
38
39         /**
40          * The icon to be used for an open bug
41          * @var OpenLayers.Icon
42         */
43         iconOpen : new OpenLayers.Icon("/images/open_bug_marker.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
44
45         /**
46          * The icon to be used for a closed bug
47          * @var OpenLayers.Icon
48         */
49         iconClosed : new OpenLayers.Icon("/images/closed_bug_marker.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
50
51         /**
52          * The projection of the coordinates sent by the OpenStreetBugs API.
53          * @var OpenLayers.Projection
54         */
55         apiProjection : new OpenLayers.Projection("EPSG:4326"),
56
57         /**
58          * If this is set to true, the user may not commit comments or close bugs.
59          * @var Boolean
60         */
61         readonly : false,
62
63         /**
64          * When the layer is hidden, all open popups are stored in this array in order to be re-opened again when the layer is made visible again.
65         */
66         reopenPopups : [ ],
67
68         /**
69          * The user name will be saved in a cookie if this isn’t set to false.
70          * @var Boolean
71         */
72         setCookie : true,
73
74         /**
75          * The lifetime of the user name cookie in days.
76          * @var Number
77         */
78         cookieLifetime : 1000,
79
80         /**
81          * The path where the cookie will be available on this server.
82          * @var String
83         */
84         cookiePath : null,
85
86         /**
87          * A URL to append lon=123&lat=123&zoom=123 for the Permalinks.
88          * @var String
89         */
90         permalinkURL : "http://www.openstreetmap.org/",
91
92         /**
93          * A CSS file to be included. Set to null if you don’t need this.
94          * @var String
95         */
96         theme : "http://osm.cdauth.de/map/openstreetbugs.css",
97
98         /**
99          * @param String name
100         */
101         initialize : function(name, options)
102         {
103                 OpenLayers.Layer.Markers.prototype.initialize.apply(this, [ name, OpenLayers.Util.extend({ opacity: 0.7, projection: new OpenLayers.Projection("EPSG:4326") }, options) ]);
104                 putAJAXMarker.layers.push(this);
105                 this.events.addEventType("markerAdded");
106
107                 this.events.register("visibilitychanged", this, this.updatePopupVisibility);
108                 this.events.register("visibilitychanged", this, this.loadBugs);
109
110                 var cookies = document.cookie.split(/;\s*/);
111                 for(var i=0; i<cookies.length; i++)
112                 {
113                         var cookie = cookies[i].split("=");
114                         if(cookie[0] == "osbUsername")
115                         {
116                                 this.username = decodeURIComponent(cookie[1]);
117                                 break;
118                         }
119                 }
120
121                 /* Copied from OpenLayers.Map */
122                 if(this.theme) {
123             // check existing links for equivalent url
124             var addNode = true;
125             var nodes = document.getElementsByTagName('link');
126             for(var i=0, len=nodes.length; i<len; ++i) {
127                 if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
128                                                    this.theme)) {
129                     addNode = false;
130                     break;
131                 }
132             }
133             // only add a new node if one with an equivalent url hasn't already
134             // been added
135             if(addNode) {
136                 var cssNode = document.createElement('link');
137                 cssNode.setAttribute('rel', 'stylesheet');
138                 cssNode.setAttribute('type', 'text/css');
139                 cssNode.setAttribute('href', this.theme);
140                 document.getElementsByTagName('head')[0].appendChild(cssNode);
141             }
142         }
143         },
144
145         /**
146          * Is automatically called when the layer is added to an OpenLayers.Map. Initialises the automatic bug loading in the visible bounding box.
147         */
148         afterAdd : function()
149         {
150                 var ret = OpenLayers.Layer.Markers.prototype.afterAdd.apply(this, arguments);
151
152                 this.map.events.register("moveend", this, this.loadBugs);
153                 this.loadBugs();
154
155                 return ret;
156         },
157
158         /**
159          * At the moment the OSB API responses to requests using JavaScript code. This way the Same Origin Policy can be worked around. Unfortunately, this makes communicating with the API a bit too asynchronous, at the moment there is no way to tell to which request the API actually responses.
160          * This method creates a new script HTML element that imports the API request URL. The API JavaScript response then executes the global functions provided below.
161          * @param String url The URL this.serverURL + url is requested.
162         */
163         apiRequest : function(url) {
164                 var script = document.createElement("script");
165                 script.type = "text/javascript";
166                 script.src = this.serverURL + url + "&nocache="+(new Date()).getTime();
167                 document.body.appendChild(script);
168         },
169
170         /**
171          * Is automatically called when the visibility of the layer changes. When the layer is hidden, all visible popups
172          * are closed and their visibility is saved. When the layer is made visible again, these popups are re-opened.
173         */
174         updatePopupVisibility : function()
175         {
176                 if(this.getVisibility())
177                 {
178                         for(var i=0; i<this.reopenPopups.length; i++)
179                                 this.reopenPopups[i].show();
180                         this.reopenPopups = [ ];
181                 }
182                 else
183                 {
184                         for(var i=0; i<this.markers.length; i++)
185                         {
186                                 if(this.markers[i].feature.popup && this.markers[i].feature.popup.visible())
187                                 {
188                                         this.markers[i].feature.popup.hide();
189                                         this.reopenPopups.push(this.markers[i].feature.popup);
190                                 }
191                         }
192                 }
193         },
194
195         /**
196          * Sets the user name to be used for interactions with OpenStreetBugs.
197         */
198         setUserName : function(username)
199         {
200                 if(this.username == username)
201                         return;
202
203                 this.username = username;
204
205                 if(this.setCookie)
206                 {
207                         var cookie = "osbUsername="+encodeURIComponent(username);
208                         if(this.cookieLifetime)
209                                 cookie += ";expires="+(new Date((new Date()).getTime() + this.cookieLifetime*86400000)).toGMTString();
210                         if(this.cookiePath)
211                                 cookie += ";path="+this.cookiePath;
212                         document.cookie = cookie;
213                 }
214
215                 for(var i=0; i<this.markers.length; i++)
216                 {
217                         if(!this.markers[i].feature.popup) continue;
218                         var els = this.markers[i].feature.popup.contentDom.getElementsByTagName("input");
219                         for(var j=0; j<els.length; j++)
220                         {
221                                 if(els[j].className != "osbUsername") continue;
222                                 els[j].value = username;
223                         }
224                 }
225         },
226
227         /**
228          * Returns the currently set username or “NoName” if none is set.
229         */
230
231         getUserName : function()
232         {
233                 if(this.username)
234                         return this.username;
235                 else
236                         return "NoName";
237         },
238
239         /**
240          * Loads the bugs in the current bounding box. Is automatically called by an event handler ("moveend" event) that is created in the afterAdd() method.
241         */
242         loadBugs : function()
243         {
244                 if(!this.getVisibility())
245                         return true;
246
247                 var bounds = this.map.getExtent();
248                 if(!bounds) return false;
249                 bounds.transform(this.map.getProjectionObject(), this.apiProjection);
250
251                 this.apiRequest("bugs"
252                         + "?bbox="+this.round(bounds.left, 5)
253             + ","+this.round(bounds.bottom, 5)
254                     + ","+this.round(bounds.right, 5)                   
255                         + ","+this.round(bounds.top, 5));
256         },
257
258         /**
259          * Rounds the given number to the given number of digits after the floating point.
260          * @param Number number
261          * @param Number digits
262          * @return Number
263         */
264         round : function(number, digits)
265         {
266                 var factor = Math.pow(10, digits);
267                 return Math.round(number*factor)/factor;
268         },
269
270         /**
271          * Adds an OpenLayers.Marker representing a bug to the map. Is usually called by loadBugs().
272          * @param Number id The bug ID
273         */
274         createMarker: function(id)
275         {
276                 if(this.bugs[id])
277                 {
278                         if(this.bugs[id].popup && !this.bugs[id].popup.visible())
279                                 this.setPopupContent(id);
280                         if(this.bugs[id].closed != putAJAXMarker.bugs[id][2])
281                                 this.bugs[id].destroy();
282                         else
283                                 return;
284                 }
285
286                 var lonlat = putAJAXMarker.bugs[id][0].clone().transform(this.apiProjection, this.map.getProjectionObject());
287                 var comments = putAJAXMarker.bugs[id][1];
288                 var closed = putAJAXMarker.bugs[id][2];
289                 var feature = new OpenLayers.Feature(this, lonlat, { icon: (closed ? this.iconClosed : this.iconOpen).clone(), autoSize: true });
290                 feature.popupClass = OpenLayers.Popup.FramedCloud.OpenStreetBugs;
291                 feature.osbId = id;
292                 feature.closed = closed;
293
294                 var marker = feature.createMarker();
295                 marker.feature = feature;
296                 marker.events.register("click", feature, this.markerClick);
297                 //marker.events.register("mouseover", feature, this.markerMouseOver);
298                 //marker.events.register("mouseout", feature, this.markerMouseOut);
299                 this.addMarker(marker);
300
301                 this.bugs[id] = feature;
302                 this.events.triggerEvent("markerAdded");
303         },
304
305         /**
306          * Recreates the content of the popup of a marker.
307          * @param Number id The bug ID
308         */
309
310         setPopupContent: function(id) {
311                 if(!this.bugs[id].popup)
312                         return;
313
314                 var el1,el2,el3;
315                 var layer = this;
316
317                 var newContent = document.createElement("div");
318
319                 el1 = document.createElement("h3");
320                 el1.appendChild(document.createTextNode(putAJAXMarker.bugs[id][2] ? i18n("javascripts.osb.Fixed Error") : i18n("javascripts.osb.Unresolved Error")));
321
322                 el1.appendChild(document.createTextNode(" ["));
323                 el2 = document.createElement("a");
324                 el2.href = "/browse/bug/" + id;
325                 el2.onclick = function(){ layer.map.setCenter(putAJAXMarker.bugs[id][0].clone().transform(layer.apiProjection, layer.map.getProjectionObject()), 15); };
326                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.Details")));
327                 el1.appendChild(el2);
328                 el1.appendChild(document.createTextNode("]"));
329
330                 if(this.permalinkURL)
331                 {
332                         el1.appendChild(document.createTextNode(" ["));
333                         el2 = document.createElement("a");
334                         el2.href = this.permalinkURL + (this.permalinkURL.indexOf("?") == -1 ? "?" : "&") + "lon="+putAJAXMarker.bugs[id][0].lon+"&lat="+putAJAXMarker.bugs[id][0].lat+"&zoom=15";
335                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Permalink")));
336                         el1.appendChild(el2);
337                         el1.appendChild(document.createTextNode("]"));
338                 }
339                 newContent.appendChild(el1);
340
341                 var containerDescription = document.createElement("div");
342                 newContent.appendChild(containerDescription);
343
344                 var containerChange = document.createElement("div");
345                 newContent.appendChild(containerChange);
346
347                 var displayDescription = function(){
348                         containerDescription.style.display = "block";
349                         containerChange.style.display = "none";
350                         layer.bugs[id].popup.updateSize();
351                 };
352                 var displayChange = function(){
353                         containerDescription.style.display = "none";
354                         containerChange.style.display = "block";
355                         layer.bugs[id].popup.updateSize();
356                 };
357                 displayDescription();
358
359                 el1 = document.createElement("dl");
360                 for(var i=0; i<putAJAXMarker.bugs[id][1].length; i++)
361                 {
362                         el2 = document.createElement("dt");
363                         el2.className = (i == 0 ? "osb-description" : "osb-comment");
364                         el2.appendChild(document.createTextNode(i == 0 ? i18n("javascripts.osb.Description") : i18n("javascripts.osb.Comment")));
365                         el1.appendChild(el2);
366                         el2 = document.createElement("dd");
367                         el2.className = (i == 0 ? "osb-description" : "osb-comment");
368                         el2.appendChild(document.createTextNode(putAJAXMarker.bugs[id][1][i]));
369                         el1.appendChild(el2);
370             if (i == 0) { el2 = document.createElement("br"); el1.appendChild(el2);};
371                 }
372                 containerDescription.appendChild(el1);
373
374                 if(putAJAXMarker.bugs[id][2])
375                 {
376                         el1 = document.createElement("p");
377                         el1.className = "osb-fixed";
378                         el2 = document.createElement("em");
379                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Has been fixed")));
380                         el1.appendChild(el2);
381                         containerDescription.appendChild(el1);
382                 }
383                 else if(!this.readonly)
384                 {
385                         el1 = document.createElement("div");
386                         el2 = document.createElement("input");
387                         el2.setAttribute("type", "button");
388                         el2.onclick = function(){ displayChange(); };
389                         el2.value = i18n("javascripts.osb.Comment/Close");
390                         el1.appendChild(el2);
391                         containerDescription.appendChild(el1);
392
393                         var el_form = document.createElement("form");
394                         el_form.onsubmit = function(){ if(inputComment.value.match(/^\s*$/)) return false; layer.submitComment(id, inputComment.value); layer.hidePopup(id); return false; };
395
396                         el1 = document.createElement("dl");
397                         el2 = document.createElement("dt");
398                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Nickname")));
399                         el1.appendChild(el2);
400                         el2 = document.createElement("dd");
401                         var inputUsername = document.createElement("input");
402                         var inputUsername = document.createElement("input");;
403                         if (typeof loginName === 'undefined') {
404                                 inputUsername.value = this.username;
405                         } else {
406                                 inputUsername.value = loginName;
407                                 inputUsername.setAttribute('disabled','true');
408                         }
409                         inputUsername.className = "osbUsername";
410                         inputUsername.onkeyup = function(){ layer.setUserName(inputUsername.value); };
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.osb.Login")));
416                         el2.appendChild(el3)
417                         el1.appendChild(el2);                   
418
419                         el2 = document.createElement("dt");
420                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.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(){ this.form.onsubmit(); return false; };
438                         el3.value = i18n("javascripts.osb.Add comment");
439                         el2.appendChild(el3);
440                         el1.appendChild(el2);
441
442                         el2 = document.createElement("li");
443                         el3 = document.createElement("input");
444                         el3.setAttribute("type", "button");
445                         el3.onclick = function(){ this.form.onsubmit(); layer.closeBug(id); layer.bugs[id].popup.hide(); return false; };
446                         el3.value = i18n("javascripts.osb.Mark as fixed");
447                         el2.appendChild(el3);
448                         el1.appendChild(el2);
449                         el_form.appendChild(el1);
450                         containerChange.appendChild(el_form);
451
452                         el1 = document.createElement("div");
453                         el2 = document.createElement("input");
454                         el2.setAttribute("type", "button");
455                         el2.onclick = function(){ displayDescription(); };
456                         el2.value = i18n("javascripts.osb.Cancel");
457                         el1.appendChild(el2);
458                         containerChange.appendChild(el1);
459                 }
460
461                 this.bugs[id].popup.setContentHTML(newContent);
462         },
463
464         /**
465          * Creates a new bug.
466          * @param OpenLayers.LonLat lonlat The coordinates in the API projection.
467          * @param String description
468         */
469         createBug: function(lonlat, description) {
470                 this.apiRequest("bug/create"
471                         + "?lat="+encodeURIComponent(lonlat.lat)
472                         + "&lon="+encodeURIComponent(lonlat.lon)
473                         + "&text="+encodeURIComponent(description)
474                         + "&name="+encodeURIComponent(this.getUserName())
475                         + "&format=js"
476                 );
477         },
478
479         /**
480          * Adds a comment to a bug.
481          * @param Number id
482          * @param String comment
483         */
484         submitComment: function(id, comment) {
485                 this.apiRequest("bug/"+encodeURIComponent(id)+"/comment"
486                         + "?text="+encodeURIComponent(comment)
487                         + "&name="+encodeURIComponent(this.getUserName())
488                         + "&format=js"
489                 );
490         },
491
492         /**
493          * Marks a bug as fixed.
494          * @param Number id
495         */
496         closeBug: function(id) {
497                 this.apiRequest("bug/"+encodeURIComponent(id)+"/close"
498                         + "?format=js"
499                 );
500         },
501
502         /**
503          * Removes the content of a marker popup (to reduce the amount of needed resources).
504          * @param Number id
505         */
506         resetPopupContent: function(id) {
507                 if(!this.bugs[id].popup)
508                         return;
509
510                 this.bugs[id].popup.setContentHTML(document.createElement("div"));
511         },
512
513         /**
514          * Makes the popup of the given marker visible. Makes sure that the popup content is created if it does not exist yet.
515          * @param Number id
516         */
517         showPopup: function(id) {
518                 var add = null;
519                 if(!this.bugs[id].popup)
520                 {
521                         add = this.bugs[id].createPopup(true);
522                         add.events.register("close", this, function(){ this.resetPopupContent(id); if(this.bugs[id].osbClicked) this.bugs[id].osbClicked = false; });
523                 }
524                 else if(this.bugs[id].popup.visible())
525                         return;
526
527                 this.setPopupContent(id);
528                 if(add)
529                         this.map.addPopup(add);
530                 this.bugs[id].popup.show();
531                 this.bugs[id].popup.updateSize();
532         },
533
534         /**
535          * Hides the popup of the given marker.
536          * @param Number id
537         */
538         hidePopup: function(id) {
539                 if(!this.bugs[id].popup || !this.bugs[id].popup.visible())
540                         return;
541
542                 this.bugs[id].popup.hide();
543                 this.bugs[id].popup.events.triggerEvent("close");
544         },
545
546         /**
547          * Is run on the “click” event of a marker in the context of its OpenLayers.Feature. Toggles the visibility of the popup.
548         */
549         markerClick: function(e) {
550                 var feature = this; // Context is the feature
551
552                 feature.osbClicked = !feature.osbClicked;
553                 if(feature.osbClicked)
554                         feature.layer.showPopup(feature.osbId);
555                 else
556                         feature.layer.hidePopup(feature.osbId);
557                 OpenLayers.Event.stop(e);
558         },
559
560         /**
561          * Is run on the “mouseover” event of a marker in the context of its OpenLayers.Feature. Makes the popup visible.
562         */
563         markerMouseOver: function(e) {
564                 var feature = this; // Context is the feature
565
566                 feature.layer.showPopup(feature.osbId);
567                 OpenLayers.Event.stop(e);
568         },
569
570         /**
571          * Is run on the “mouseout” event of a marker in the context of its OpenLayers.Feature. Hides the popup (if it has not been clicked).
572         */
573         markerMouseOut: function(e) {
574                 var feature = this; // Context is the feature
575
576                 if(!feature.osbClicked)
577                         feature.layer.hidePopup(feature.osbId);
578                 OpenLayers.Event.stop(e);
579         },
580
581         CLASS_NAME: "OpenLayers.Layer.OpenStreetBugs"
582 });
583
584 /**
585  * An OpenLayers control to create new bugs on mouse clicks on the map. Add an instance of this to your map using
586  * the OpenLayers.Map.addControl() method and activate() it.
587 */
588
589 OpenLayers.Control.OpenStreetBugs = new OpenLayers.Class(OpenLayers.Control, {
590         title : null, // See below because of translation call
591
592         /**
593          * The icon to be used for the temporary markers that the “create bug” popup belongs to.
594          * @var OpenLayers.Icon
595         */
596         icon : new OpenLayers.Icon("/images/icon_error_add.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
597
598         /**
599          * An instance of the OpenStreetBugs layer that this control shall be connected to. Is set in the constructor.
600          * @var OpenLayers.Layer.OpenStreetBugs
601         */
602         osbLayer : null,
603
604         /**
605          * @param OpenLayers.Layer.OpenStreetBugs osbLayer The OpenStreetBugs layer that this control will be connected to.
606         */
607         initialize: function(osbLayer, options) {
608                 this.osbLayer = osbLayer;
609
610                 this.title = i18n("javascripts.osb.Create OpenStreetBug");
611
612                 OpenLayers.Control.prototype.initialize.apply(this, [ options ]);
613
614                 this.events.register("activate", this, function() {
615                         if(!this.osbLayer.getVisibility())
616                                 this.osbLayer.setVisibility(true);
617                 });
618
619                 this.osbLayer.events.register("visibilitychanged", this, function() {
620                         if(this.active && !this.osbLayer.getVisibility())
621                                 this.osbLayer.setVisibility(true);
622                 });
623         },
624
625         destroy: function() {
626                 if (this.handler)
627                         this.handler.destroy();
628                 this.handler = null;
629
630                 OpenLayers.Control.prototype.destroy.apply(this, arguments);
631         },
632
633         draw: function() {
634                 this.handler = new OpenLayers.Handler.Click(this, {'click': this.click}, { 'single': true, 'double': false, 'pixelTolerance': 0, 'stopSingle': false, 'stopDouble': false });
635         },
636
637         /**
638          * Map clicking event handler. Adds a temporary marker with a popup to the map, the popup contains the form to add a bug.
639         */
640         click: function(e) {
641                 if(!this.map) return true;
642                 deactivateControl();
643
644                 var control = this;
645                 var lonlat = this.map.getLonLatFromViewPortPx(e.xy);
646                 var lonlatApi = lonlat.clone().transform(this.map.getProjectionObject(), this.osbLayer.apiProjection);
647                 var feature = new OpenLayers.Feature(this.osbLayer, lonlat, { icon: this.icon.clone(), autoSize: true });
648                 feature.popupClass = OpenLayers.Popup.FramedCloud.OpenStreetBugs;
649                 var marker = feature.createMarker();
650                 marker.feature = feature;
651                 this.osbLayer.addMarker(marker);
652
653                 var newContent = document.createElement("div");
654                 var el1,el2,el3;
655                 el1 = document.createElement("h3");
656                 el1.appendChild(document.createTextNode(i18n("javascripts.osb.Create bug")));
657                 newContent.appendChild(el1);
658
659                 var el_form = document.createElement("form");
660
661                 el1 = document.createElement("dl");
662                 el2 = document.createElement("dt");
663                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.Nickname")));
664                 el1.appendChild(el2);
665                 el2 = document.createElement("dd");
666                 var inputUsername = document.createElement("input");;
667                 if (typeof loginName === 'undefined') {
668                     inputUsername.value = this.osbLayer.username;
669                 } else {
670                         inputUsername.value = loginName;
671                         inputUsername.setAttribute('disabled','true');
672                 }               
673                 inputUsername.className = "osbUsername";
674                 
675                 inputUsername.onkeyup = function(){ control.osbLayer.setUserName(inputUsername.value); };
676                 el2.appendChild(inputUsername);
677                 el3 = document.createElement("a");
678                 el3.setAttribute("href","login");
679                 el3.className = "hide_if_logged_in";
680                 el3.appendChild(document.createTextNode(i18n("javascripts.osb.Login")));
681                 el2.appendChild(el3);
682                 el1.appendChild(el2);
683                 el2 = document.createElement("br");
684         el1.appendChild(el2);
685
686                 el2 = document.createElement("dt");
687                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.Bug description")));
688                 el1.appendChild(el2);
689                 el2 = document.createElement("dd");
690                 var inputDescription = document.createElement("textarea");
691                 inputDescription.setAttribute("cols",40);
692                 inputDescription.setAttribute("rows",3);
693                 el2.appendChild(inputDescription);
694                 el1.appendChild(el2);
695                 el_form.appendChild(el1);
696
697                 el1 = document.createElement("div");
698                 el2 = document.createElement("input");
699                 el2.setAttribute("type", "button");
700                 el2.value = i18n("javascripts.osb.Create");
701         el2.onclick = function() { control.osbLayer.createBug(lonlatApi, inputDescription.value); marker.feature = null; feature.destroy(); return false; };
702                 el1.appendChild(el2);
703                 el2 = document.createElement("input");
704                 el2.setAttribute("type", "button");
705                 el2.value = i18n("javascripts.osb.Cancel");
706                 el2.onclick = function(){ feature.destroy(); };
707                 el1.appendChild(el2);
708                 el_form.appendChild(el1);
709                 newContent.appendChild(el_form);
710
711                 el2 = document.createElement("hr");
712                 el1.appendChild(el2);
713                 el2 = document.createElement("a");
714                 el2.setAttribute("href","edit");
715                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.edityourself")));
716                 el1.appendChild(el2);
717
718                 feature.data.popupContentHTML = newContent;
719                 var popup = feature.createPopup(true);
720                 popup.events.register("close", this, function(){ feature.destroy(); });
721                 this.map.addPopup(popup);
722                 popup.updateSize();
723         },
724
725         CLASS_NAME: "OpenLayers.Control.OpenStreetBugs"
726 });
727
728
729 /**
730  * This class changes the usual OpenLayers.Popup.FramedCloud class by using a DOM element instead of an innerHTML string as content for the popup.
731  * This is necessary for creating valid onclick handlers that still work with multiple OpenStreetBugs layer objects.
732 */
733
734 OpenLayers.Popup.FramedCloud.OpenStreetBugs = new OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
735         contentDom : null,
736         autoSize : true,
737
738         /**
739          * See OpenLayers.Popup.FramedCloud.initialize() for parameters. As fourth parameter, pass a DOM node instead of a string.
740         */
741         initialize: function() {
742                 this.displayClass = this.displayClass + " " + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
743
744                 var args = new Array(arguments.length);
745                 for(var i=0; i<arguments.length; i++)
746                         args[i] = arguments[i];
747
748                 // Unset original contentHTML parameter
749                 args[3] = null;
750
751                 var closeCallback = arguments[6];
752
753                 // Add close event trigger to the closeBoxCallback parameter
754                 args[6] = function(e){ if(closeCallback) closeCallback(); else this.hide(); OpenLayers.Event.stop(e); this.events.triggerEvent("close"); };
755
756                 OpenLayers.Popup.FramedCloud.prototype.initialize.apply(this, args);
757
758                 this.events.addEventType("close");
759
760                 this.setContentHTML(arguments[3]);
761         },
762
763         /**
764          * Like OpenLayers.Popup.FramedCloud.setContentHTML(), but takes a DOM element as parameter.
765         */
766         setContentHTML: function(contentDom) {
767                 if(contentDom != null)
768                         this.contentDom = contentDom;
769
770                 if(this.contentDiv == null || this.contentDom == null || this.contentDom == this.contentDiv.firstChild)
771                         return;
772
773                 while(this.contentDiv.firstChild)
774                         this.contentDiv.removeChild(this.contentDiv.firstChild);
775
776                 this.contentDiv.appendChild(this.contentDom);
777
778                 // Copied from OpenLayers.Popup.setContentHTML():
779                 if(this.autoSize)
780                 {
781                         this.registerImageListeners();
782                         this.updateSize();
783                 }
784         },
785
786         destroy: function() {
787                 this.contentDom = null;
788                 OpenLayers.Popup.FramedCloud.prototype.destroy.apply(this, arguments);
789         },
790
791         CLASS_NAME: "OpenLayers.Popup.FramedCloud.OpenStreetBugs"
792 });
793
794
795 /**
796  * This global function is executed by the OpenStreetBugs API getBugs script.
797  * Each OpenStreetBugs layer adds itself to the putAJAXMarker.layer array. The putAJAXMarker() function executes the createMarker() method
798  * on each layer in that array each time it is called. This has the side-effect that bugs displayed in one map on a page are already loaded
799  * on the other map as well.
800 */
801
802 function putAJAXMarker(id, lon, lat, text, closed)
803 {
804         var comments = text.split(/<hr \/>/);
805         for(var i=0; i<comments.length; i++)
806                 comments[i] = comments[i].replace(/&quot;/g, "\"").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
807         putAJAXMarker.bugs[id] = [
808                 new OpenLayers.LonLat(lon, lat),
809                 comments,
810                 closed
811         ];
812         for(var i=0; i<putAJAXMarker.layers.length; i++)
813                 putAJAXMarker.layers[i].createMarker(id);
814 }
815
816 /**
817  * This global function is executed by the OpenStreetBugs API. The “create bug”, “comment” and “close bug” scripts execute it to give information about their success.
818  * In case of success, this function is called without a parameter, in case of an error, the error message is passed. This is lousy workaround to make it any functional at all, the OSB API is likely to be extended later (then it will provide additional information such as the ID of a created bug and similar).
819 */
820
821 function osbResponse(error)
822 {
823         if(error)
824                 alert("Error: "+error);
825
826         for(var i=0; i<putAJAXMarker.layers.length; i++)
827                 putAJAXMarker.layers[i].loadBugs();
828 }
829
830 putAJAXMarker.layers = [ ];
831 putAJAXMarker.bugs = { };
832
833 function deactivateControl() { 
834     map.osbControl.deactivate(); 
835     document.getElementById("OpenLayers.Map_18_OpenLayers_Container").style.cursor = "default"; 
836   }
837
838
839 /* Translations */
840
841 /*
842
843 OpenLayers.Lang.en = OpenLayers.Util.extend(OpenLayers.Lang.en, {
844         "Fixed Error" : "Fixed Error",
845         "Unresolved Error" : "Unresolved Error",
846         "Description" : "Description",
847         "Comment" : "Comment",
848         "Has been fixed." : "This error has been fixed already. However, it might take a couple of days before the map image is updated.",
849         "Comment/Close" : "Comment/Close",
850         "Nickname" : "Nickname",
851         "Add comment" : "Add comment",
852         "Mark as fixed" : "Mark as fixed",
853         "Cancel" : "Cancel",
854         "Create OpenStreetBug" : "Create OpenStreetBug",
855         "Create bug" : "Create bug",
856         "Bug description" : "Bug description",
857         "Create" : "Create",
858         "Permalink" : "Permalink",
859         "Zoom" : "Zoom"
860 });
861
862 OpenLayers.Lang.de = OpenLayers.Util.extend(OpenLayers.Lang.de, {
863         "Fixed Error" : "Behobener Fehler",
864         "Unresolved Error" : "Offener Fehler",
865         "Description" : "Beschreibung",
866         "Comment" : "Kommentar",
867         "Has been fixed." : "Der Fehler wurde bereits behoben. Es kann jedoch bis zu einigen Tagen dauern, bis die Kartenansicht aktualisiert wird.",
868         "Comment/Close" : "Kommentieren/Schließen",
869         "Nickname" : "Benutzername",
870         "Add comment" : "Kommentar hinzufügen",
871         "Mark as fixed" : "Als behoben markieren",
872         "Cancel" : "Abbrechen",
873         "Create OpenStreetBug" : "OpenStreetBug melden",
874         "Create bug" : "Bug anlegen",
875         "Bug description" : "Fehlerbeschreibung",
876         "Create" : "Anlegen",
877         "Permalink" : "Permalink",
878         "Zoom" : "Zoom"
879 });
880
881 OpenLayers.Lang.fr = OpenLayers.Util.extend(OpenLayers.Lang.fr, {
882         "Fixed Error" : "Erreur corrigée",
883         "Unresolved Error" : "Erreur non corrigée",
884         "Description" : "Description",
885         "Comment" : "Commentaire",
886         "Has been fixed." : "Cette erreur a déjà été corrigée. Cependant, il peut être nécessaire d'attendre quelques jours avant que l'image de la carte ne soit mise à jour.",
887         "Comment/Close" : "Commenter/Fermer",
888         "Nickname" : "Surnom",
889         "Add comment" : "Ajouter un commentaire",
890         "Mark as fixed" : "Marquer comme corrigé",
891         "Cancel" : "Annuler",
892         "Create OpenStreetBug" : "Créer OpenStreetBug",
893         "Create bug" : "Ajouter un bug",
894         "Bug description" : "Description du bug",
895         "Create" : "Créer",
896         "Permalink" : "Lien permanent",
897         "Zoom" : "Zoom"
898 });
899
900 OpenLayers.Lang.nl = OpenLayers.Util.extend(OpenLayers.Lang.nl, {
901         "Fixed Error" : "Fout verholpen",
902         "Unresolved Error" : "Openstaande fout",
903         "Description" : "Beschrijving",
904         "Comment" : "Kommentaar",
905         "Has been fixed." : "De fout is al eerder opgelost. Het kan echter nog een paar dagen duren voordat het kaartmateriaal geactualiseerd is.",
906         "Comment/Close" : "Bekommentariëren/Sluiten",
907         "Nickname" : "Gebruikersnaam",
908         "Add comment" : "Kommentaar toevoegen",
909         "Mark as fixed" : "Als opgelost aanmerken",
910         "Cancel" : "Afbreken",
911         "Create OpenStreetBug" : "OpenStreetBug melden",
912         "Create bug" : "Bug melden",
913         "Bug description" : "Foutomschrijving",
914         "Create" : "Aanmaken",
915         "Permalink" : "Permalink",
916         "Zoom" : "Zoom"
917 });
918
919 OpenLayers.Lang.it = OpenLayers.Util.extend(OpenLayers.Lang.it, {
920         "Fixed Error" : "Sbaglio coretto",
921         "Unresolved Error" : "Sbaglio non coretto",
922         "Description" : "Descrizione",
923         "Comment" : "Commento",
924         "Has been fixed." : "Questo sbaglio è già coretto. Forse ci metto qualche giorni per aggiornare anche i quadri.",
925         "Comment/Close" : "Commenta/Chiude",
926         "Nickname" : "Nome",
927         "Add comment" : "Aggiunge commento",
928         "Mark as fixed" : "Marca che è coretto",
929         "Cancel" : "Annulla",
930         "Create OpenStreetBug" : "Aggiunge OpenStreetBug",
931         "Create bug" : "Aggiunge un sbaglio",
932         "Bug description" : "Descrizione del sbaglio",
933         "Create" : "Aggiunge",
934         "Permalink" : "Permalink",
935         "Zoom" : "Zoom"
936 });
937
938 OpenLayers.Lang.ro = OpenLayers.Util.extend(OpenLayers.Lang.ro, {
939         "Fixed Error" : "Eroare rezolvată",
940         "Unresolved Error" : "Eroare nerezolvată",
941         "Description" : "Descriere",
942         "Comment" : "Comentariu",
943         "Has been fixed." : "Această eroare a fost rezolvată. Totuși este posibil să dureze câteva zile până când imaginea hărții va fi actualizată.",
944         "Comment/Close" : "Comentariu/Închide",
945         "Nickname" : "Nume",
946         "Add comment" : "Adaugă comentariu",
947         "Mark as fixed" : "Marchează ca rezolvată",
948         "Cancel" : "Anulează",
949         "Create OpenStreetBug" : "Crează OpenStreetBug",
950         "Create bug" : "Adaugă eroare",
951         "Bug description" : "Descrierea erorii",
952         "Create" : "Adaugă",
953         "Permalink" : "Permalink",
954         "Zoom" : "Zoom"
955 });
956 */