378c2ce323cba80db0b9def2398d7db4f1fd57b1
[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(closed ? 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                 }
371                 containerDescription.appendChild(el1);
372
373                 if(putAJAXMarker.bugs[id][2])
374                 {
375                         el1 = document.createElement("p");
376                         el1.className = "osb-fixed";
377                         el2 = document.createElement("em");
378                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Has been fixed.")));
379                         el1.appendChild(el2);
380                         containerDescription.appendChild(el1);
381                 }
382                 else if(!this.readonly)
383                 {
384                         el1 = document.createElement("div");
385                         el2 = document.createElement("input");
386                         el2.setAttribute("type", "button");
387                         el2.onclick = function(){ displayChange(); };
388                         el2.value = i18n("javascripts.osb.Comment/Close");
389                         el1.appendChild(el2);
390                         containerDescription.appendChild(el1);
391
392                         var el_form = document.createElement("form");
393                         el_form.onsubmit = function(){ if(inputComment.value.match(/^\s*$/)) return false; layer.submitComment(id, inputComment.value); layer.hidePopup(id); return false; };
394
395                         el1 = document.createElement("dl");
396                         el2 = document.createElement("dt");
397                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Nickname")));
398                         el1.appendChild(el2);
399                         el2 = document.createElement("dd");
400                         var inputUsername = document.createElement("input");
401                         var inputUsername = document.createElement("input");;
402                         if (typeof loginName === 'undefined') {
403                                 inputUsername.value = this.username;
404                         } else {
405                                 inputUsername.value = loginName;
406                                 inputUsername.setAttribute('disabled','true');
407                         }
408                         inputUsername.className = "osbUsername";
409                         inputUsername.onkeyup = function(){ layer.setUserName(inputUsername.value); };
410                         el2.appendChild(inputUsername);
411                         el3 = document.createElement("a");
412                         el3.setAttribute("href","login");
413                         el3.className = "hide_if_logged_in";
414                         el3.appendChild(document.createTextNode(i18n("javascripts.osb.Login")));
415                         el2.appendChild(el3)
416                         el1.appendChild(el2);                   
417
418                         el2 = document.createElement("dt");
419                         el2.appendChild(document.createTextNode(i18n("javascripts.osb.Comment")));
420                         el1.appendChild(el2);
421                         el2 = document.createElement("dd");
422                         var inputComment = document.createElement("textarea");
423                         inputComment.setAttribute("cols",40);                   
424                         el2.appendChild(inputComment);
425                         el1.appendChild(el2);
426                         
427                         el_form.appendChild(el1);
428
429                         el1 = document.createElement("ul");
430                         el1.className = "buttons";
431                         el2 = document.createElement("li");
432                         el3 = document.createElement("input");
433                         el3.setAttribute("type", "submit");
434                         el3.value = i18n("javascripts.osb.Add comment");
435                         el2.appendChild(el3);
436                         el1.appendChild(el2);
437
438                         el2 = document.createElement("li");
439                         el3 = document.createElement("input");
440                         el3.setAttribute("type", "button");
441                         el3.onclick = function(){ this.form.onsubmit(); layer.closeBug(id); layer.bugs[id].popup.hide(); return false; };
442                         el3.value = i18n("javascripts.osb.Mark as fixed");
443                         el2.appendChild(el3);
444                         el1.appendChild(el2);
445                         el_form.appendChild(el1);
446                         containerChange.appendChild(el_form);
447
448                         el1 = document.createElement("div");
449                         el2 = document.createElement("input");
450                         el2.setAttribute("type", "button");
451                         el2.onclick = function(){ displayDescription(); };
452                         el2.value = i18n("javascripts.osb.Cancel");
453                         el1.appendChild(el2);
454                         containerChange.appendChild(el1);
455                 }
456
457                 this.bugs[id].popup.setContentHTML(newContent);
458         },
459
460         /**
461          * Creates a new bug.
462          * @param OpenLayers.LonLat lonlat The coordinates in the API projection.
463          * @param String description
464         */
465         createBug: function(lonlat, description) {
466                 this.apiRequest("bug/create"
467                         + "?lat="+encodeURIComponent(lonlat.lat)
468                         + "&lon="+encodeURIComponent(lonlat.lon)
469                         + "&text="+encodeURIComponent(description)
470                         + "&name="+encodeURIComponent(this.getUserName())
471                         + "&format=js"
472                 );
473         },
474
475         /**
476          * Adds a comment to a bug.
477          * @param Number id
478          * @param String comment
479         */
480         submitComment: function(id, comment) {
481                 this.apiRequest("bug/"+encodeURIComponent(id)+"/comment"
482                         + "?text="+encodeURIComponent(comment)
483                         + "&name="+encodeURIComponent(this.getUserName())
484                         + "&format=js"
485                 );
486         },
487
488         /**
489          * Marks a bug as fixed.
490          * @param Number id
491         */
492         closeBug: function(id) {
493                 this.apiRequest("bug/"+encodeURIComponent(id)+"/close"
494                         + "?format=js"
495                 );
496         },
497
498         /**
499          * Removes the content of a marker popup (to reduce the amount of needed resources).
500          * @param Number id
501         */
502         resetPopupContent: function(id) {
503                 if(!this.bugs[id].popup)
504                         return;
505
506                 this.bugs[id].popup.setContentHTML(document.createElement("div"));
507         },
508
509         /**
510          * Makes the popup of the given marker visible. Makes sure that the popup content is created if it does not exist yet.
511          * @param Number id
512         */
513         showPopup: function(id) {
514                 var add = null;
515                 if(!this.bugs[id].popup)
516                 {
517                         add = this.bugs[id].createPopup(true);
518                         add.events.register("close", this, function(){ this.resetPopupContent(id); if(this.bugs[id].osbClicked) this.bugs[id].osbClicked = false; });
519                 }
520                 else if(this.bugs[id].popup.visible())
521                         return;
522
523                 this.setPopupContent(id);
524                 if(add)
525                         this.map.addPopup(add);
526                 this.bugs[id].popup.show();
527                 this.bugs[id].popup.updateSize();
528         },
529
530         /**
531          * Hides the popup of the given marker.
532          * @param Number id
533         */
534         hidePopup: function(id) {
535                 if(!this.bugs[id].popup || !this.bugs[id].popup.visible())
536                         return;
537
538                 this.bugs[id].popup.hide();
539                 this.bugs[id].popup.events.triggerEvent("close");
540         },
541
542         /**
543          * Is run on the “click” event of a marker in the context of its OpenLayers.Feature. Toggles the visibility of the popup.
544         */
545         markerClick: function(e) {
546                 var feature = this; // Context is the feature
547
548                 feature.osbClicked = !feature.osbClicked;
549                 if(feature.osbClicked)
550                         feature.layer.showPopup(feature.osbId);
551                 else
552                         feature.layer.hidePopup(feature.osbId);
553                 OpenLayers.Event.stop(e);
554         },
555
556         /**
557          * Is run on the “mouseover” event of a marker in the context of its OpenLayers.Feature. Makes the popup visible.
558         */
559         markerMouseOver: function(e) {
560                 var feature = this; // Context is the feature
561
562                 feature.layer.showPopup(feature.osbId);
563                 OpenLayers.Event.stop(e);
564         },
565
566         /**
567          * 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).
568         */
569         markerMouseOut: function(e) {
570                 var feature = this; // Context is the feature
571
572                 if(!feature.osbClicked)
573                         feature.layer.hidePopup(feature.osbId);
574                 OpenLayers.Event.stop(e);
575         },
576
577         CLASS_NAME: "OpenLayers.Layer.OpenStreetBugs"
578 });
579
580 /**
581  * An OpenLayers control to create new bugs on mouse clicks on the map. Add an instance of this to your map using
582  * the OpenLayers.Map.addControl() method and activate() it.
583 */
584
585 OpenLayers.Control.OpenStreetBugs = new OpenLayers.Class(OpenLayers.Control, {
586         title : null, // See below because of translation call
587
588         /**
589          * The icon to be used for the temporary markers that the “create bug” popup belongs to.
590          * @var OpenLayers.Icon
591         */
592         icon : new OpenLayers.Icon("/images/icon_error_add.png", new OpenLayers.Size(22, 22), new OpenLayers.Pixel(-11, -11)),
593
594         /**
595          * An instance of the OpenStreetBugs layer that this control shall be connected to. Is set in the constructor.
596          * @var OpenLayers.Layer.OpenStreetBugs
597         */
598         osbLayer : null,
599
600         /**
601          * @param OpenLayers.Layer.OpenStreetBugs osbLayer The OpenStreetBugs layer that this control will be connected to.
602         */
603         initialize: function(osbLayer, options) {
604                 this.osbLayer = osbLayer;
605
606                 this.title = i18n("javascripts.osb.Create OpenStreetBug");
607
608                 OpenLayers.Control.prototype.initialize.apply(this, [ options ]);
609
610                 this.events.register("activate", this, function() {
611                         if(!this.osbLayer.getVisibility())
612                                 this.osbLayer.setVisibility(true);
613                 });
614
615                 this.osbLayer.events.register("visibilitychanged", this, function() {
616                         if(this.active && !this.osbLayer.getVisibility())
617                                 this.osbLayer.setVisibility(true);
618                 });
619         },
620
621         destroy: function() {
622                 if (this.handler)
623                         this.handler.destroy();
624                 this.handler = null;
625
626                 OpenLayers.Control.prototype.destroy.apply(this, arguments);
627         },
628
629         draw: function() {
630                 this.handler = new OpenLayers.Handler.Click(this, {'click': this.click}, { 'single': true, 'double': false, 'pixelTolerance': 0, 'stopSingle': false, 'stopDouble': false });
631         },
632
633         /**
634          * Map clicking event handler. Adds a temporary marker with a popup to the map, the popup contains the form to add a bug.
635         */
636         click: function(e) {
637                 if(!this.map) return true;
638                 deactivateControl();
639
640                 var control = this;
641                 var lonlat = this.map.getLonLatFromViewPortPx(e.xy);
642                 var lonlatApi = lonlat.clone().transform(this.map.getProjectionObject(), this.osbLayer.apiProjection);
643                 var feature = new OpenLayers.Feature(this.osbLayer, lonlat, { icon: this.icon.clone(), autoSize: true });
644                 feature.popupClass = OpenLayers.Popup.FramedCloud.OpenStreetBugs;
645                 var marker = feature.createMarker();
646                 marker.feature = feature;
647                 this.osbLayer.addMarker(marker);
648
649                 var newContent = document.createElement("div");
650                 var el1,el2,el3;
651                 el1 = document.createElement("h3");
652                 el1.appendChild(document.createTextNode(i18n("javascripts.osb.Create bug")));
653                 newContent.appendChild(el1);
654
655                 var el_form = document.createElement("form");
656                 el_form.onsubmit = function() { control.osbLayer.createBug(lonlatApi, inputDescription.value); marker.feature = null; feature.destroy(); return false; };
657
658                 el1 = document.createElement("dl");
659                 el2 = document.createElement("dt");
660                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.Nickname")));
661                 el1.appendChild(el2);
662                 el2 = document.createElement("dd");
663                 var inputUsername = document.createElement("input");;
664                 if (typeof loginName === 'undefined') {
665                     inputUsername.value = this.osbLayer.username;
666                 } else {
667                         inputUsername.value = loginName;
668                         inputUsername.setAttribute('disabled','true');
669                 }               
670                 inputUsername.className = "osbUsername";
671                 
672                 inputUsername.onkeyup = function(){ control.osbLayer.setUserName(inputUsername.value); };
673                 el2.appendChild(inputUsername);
674                 el3 = document.createElement("a");
675                 el3.setAttribute("href","login");
676                 el3.className = "hide_if_logged_in";
677                 el3.appendChild(document.createTextNode(i18n("javascripts.osb.Login")));
678                 el2.appendChild(el3);
679                 el1.appendChild(el2);
680
681                 el2 = document.createElement("dt");
682                 el2.appendChild(document.createTextNode(i18n("javascripts.osb.Bug description")));
683                 el1.appendChild(el2);
684                 el2 = document.createElement("dd");
685                 var inputDescription = document.createElement("textarea");
686                 inputDescription.setAttribute("cols",40);
687                 el2.appendChild(inputDescription);
688                 el1.appendChild(el2);
689                 el_form.appendChild(el1);
690
691                 el1 = document.createElement("div");
692                 el2 = document.createElement("input");
693                 el2.setAttribute("type", "submit");
694                 el2.value = i18n("javascripts.osb.Create");
695                 el1.appendChild(el2);
696                 el_form.appendChild(el1);
697                 newContent.appendChild(el_form);
698
699                 feature.data.popupContentHTML = newContent;
700                 var popup = feature.createPopup(true);
701                 popup.events.register("close", this, function(){ feature.destroy(); });
702                 this.map.addPopup(popup);
703                 popup.updateSize();
704         },
705
706         CLASS_NAME: "OpenLayers.Control.OpenStreetBugs"
707 });
708
709
710 /**
711  * This class changes the usual OpenLayers.Popup.FramedCloud class by using a DOM element instead of an innerHTML string as content for the popup.
712  * This is necessary for creating valid onclick handlers that still work with multiple OpenStreetBugs layer objects.
713 */
714
715 OpenLayers.Popup.FramedCloud.OpenStreetBugs = new OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
716         contentDom : null,
717         autoSize : true,
718
719         /**
720          * See OpenLayers.Popup.FramedCloud.initialize() for parameters. As fourth parameter, pass a DOM node instead of a string.
721         */
722         initialize: function() {
723                 this.displayClass = this.displayClass + " " + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
724
725                 var args = new Array(arguments.length);
726                 for(var i=0; i<arguments.length; i++)
727                         args[i] = arguments[i];
728
729                 // Unset original contentHTML parameter
730                 args[3] = null;
731
732                 var closeCallback = arguments[6];
733
734                 // Add close event trigger to the closeBoxCallback parameter
735                 args[6] = function(e){ if(closeCallback) closeCallback(); else this.hide(); OpenLayers.Event.stop(e); this.events.triggerEvent("close"); };
736
737                 OpenLayers.Popup.FramedCloud.prototype.initialize.apply(this, args);
738
739                 this.events.addEventType("close");
740
741                 this.setContentHTML(arguments[3]);
742         },
743
744         /**
745          * Like OpenLayers.Popup.FramedCloud.setContentHTML(), but takes a DOM element as parameter.
746         */
747         setContentHTML: function(contentDom) {
748                 if(contentDom != null)
749                         this.contentDom = contentDom;
750
751                 if(this.contentDiv == null || this.contentDom == null || this.contentDom == this.contentDiv.firstChild)
752                         return;
753
754                 while(this.contentDiv.firstChild)
755                         this.contentDiv.removeChild(this.contentDiv.firstChild);
756
757                 this.contentDiv.appendChild(this.contentDom);
758
759                 // Copied from OpenLayers.Popup.setContentHTML():
760                 if(this.autoSize)
761                 {
762                         this.registerImageListeners();
763                         this.updateSize();
764                 }
765         },
766
767         destroy: function() {
768                 this.contentDom = null;
769                 OpenLayers.Popup.FramedCloud.prototype.destroy.apply(this, arguments);
770         },
771
772         CLASS_NAME: "OpenLayers.Popup.FramedCloud.OpenStreetBugs"
773 });
774
775
776 /**
777  * This global function is executed by the OpenStreetBugs API getBugs script.
778  * Each OpenStreetBugs layer adds itself to the putAJAXMarker.layer array. The putAJAXMarker() function executes the createMarker() method
779  * 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
780  * on the other map as well.
781 */
782
783 function putAJAXMarker(id, lon, lat, text, closed)
784 {
785         var comments = text.split(/<hr \/>/);
786         for(var i=0; i<comments.length; i++)
787                 comments[i] = comments[i].replace(/&quot;/g, "\"").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
788         putAJAXMarker.bugs[id] = [
789                 new OpenLayers.LonLat(lon, lat),
790                 comments,
791                 closed
792         ];
793         for(var i=0; i<putAJAXMarker.layers.length; i++)
794                 putAJAXMarker.layers[i].createMarker(id);
795 }
796
797 /**
798  * 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.
799  * 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).
800 */
801
802 function osbResponse(error)
803 {
804         if(error)
805                 alert("Error: "+error);
806
807         for(var i=0; i<putAJAXMarker.layers.length; i++)
808                 putAJAXMarker.layers[i].loadBugs();
809 }
810
811 putAJAXMarker.layers = [ ];
812 putAJAXMarker.bugs = { };
813
814 function deactivateControl() { 
815     map.osbControl.deactivate(); 
816     document.getElementById("OpenLayers.Map_18_OpenLayers_Container").style.cursor = "default"; 
817   }
818
819
820 /* Translations */
821
822 /*
823
824 OpenLayers.Lang.en = OpenLayers.Util.extend(OpenLayers.Lang.en, {
825         "Fixed Error" : "Fixed Error",
826         "Unresolved Error" : "Unresolved Error",
827         "Description" : "Description",
828         "Comment" : "Comment",
829         "Has been fixed." : "This error has been fixed already. However, it might take a couple of days before the map image is updated.",
830         "Comment/Close" : "Comment/Close",
831         "Nickname" : "Nickname",
832         "Add comment" : "Add comment",
833         "Mark as fixed" : "Mark as fixed",
834         "Cancel" : "Cancel",
835         "Create OpenStreetBug" : "Create OpenStreetBug",
836         "Create bug" : "Create bug",
837         "Bug description" : "Bug description",
838         "Create" : "Create",
839         "Permalink" : "Permalink",
840         "Zoom" : "Zoom"
841 });
842
843 OpenLayers.Lang.de = OpenLayers.Util.extend(OpenLayers.Lang.de, {
844         "Fixed Error" : "Behobener Fehler",
845         "Unresolved Error" : "Offener Fehler",
846         "Description" : "Beschreibung",
847         "Comment" : "Kommentar",
848         "Has been fixed." : "Der Fehler wurde bereits behoben. Es kann jedoch bis zu einigen Tagen dauern, bis die Kartenansicht aktualisiert wird.",
849         "Comment/Close" : "Kommentieren/Schließen",
850         "Nickname" : "Benutzername",
851         "Add comment" : "Kommentar hinzufügen",
852         "Mark as fixed" : "Als behoben markieren",
853         "Cancel" : "Abbrechen",
854         "Create OpenStreetBug" : "OpenStreetBug melden",
855         "Create bug" : "Bug anlegen",
856         "Bug description" : "Fehlerbeschreibung",
857         "Create" : "Anlegen",
858         "Permalink" : "Permalink",
859         "Zoom" : "Zoom"
860 });
861
862 OpenLayers.Lang.fr = OpenLayers.Util.extend(OpenLayers.Lang.fr, {
863         "Fixed Error" : "Erreur corrigée",
864         "Unresolved Error" : "Erreur non corrigée",
865         "Description" : "Description",
866         "Comment" : "Commentaire",
867         "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.",
868         "Comment/Close" : "Commenter/Fermer",
869         "Nickname" : "Surnom",
870         "Add comment" : "Ajouter un commentaire",
871         "Mark as fixed" : "Marquer comme corrigé",
872         "Cancel" : "Annuler",
873         "Create OpenStreetBug" : "Créer OpenStreetBug",
874         "Create bug" : "Ajouter un bug",
875         "Bug description" : "Description du bug",
876         "Create" : "Créer",
877         "Permalink" : "Lien permanent",
878         "Zoom" : "Zoom"
879 });
880
881 OpenLayers.Lang.nl = OpenLayers.Util.extend(OpenLayers.Lang.nl, {
882         "Fixed Error" : "Fout verholpen",
883         "Unresolved Error" : "Openstaande fout",
884         "Description" : "Beschrijving",
885         "Comment" : "Kommentaar",
886         "Has been fixed." : "De fout is al eerder opgelost. Het kan echter nog een paar dagen duren voordat het kaartmateriaal geactualiseerd is.",
887         "Comment/Close" : "Bekommentariëren/Sluiten",
888         "Nickname" : "Gebruikersnaam",
889         "Add comment" : "Kommentaar toevoegen",
890         "Mark as fixed" : "Als opgelost aanmerken",
891         "Cancel" : "Afbreken",
892         "Create OpenStreetBug" : "OpenStreetBug melden",
893         "Create bug" : "Bug melden",
894         "Bug description" : "Foutomschrijving",
895         "Create" : "Aanmaken",
896         "Permalink" : "Permalink",
897         "Zoom" : "Zoom"
898 });
899
900 OpenLayers.Lang.it = OpenLayers.Util.extend(OpenLayers.Lang.it, {
901         "Fixed Error" : "Sbaglio coretto",
902         "Unresolved Error" : "Sbaglio non coretto",
903         "Description" : "Descrizione",
904         "Comment" : "Commento",
905         "Has been fixed." : "Questo sbaglio è già coretto. Forse ci metto qualche giorni per aggiornare anche i quadri.",
906         "Comment/Close" : "Commenta/Chiude",
907         "Nickname" : "Nome",
908         "Add comment" : "Aggiunge commento",
909         "Mark as fixed" : "Marca che è coretto",
910         "Cancel" : "Annulla",
911         "Create OpenStreetBug" : "Aggiunge OpenStreetBug",
912         "Create bug" : "Aggiunge un sbaglio",
913         "Bug description" : "Descrizione del sbaglio",
914         "Create" : "Aggiunge",
915         "Permalink" : "Permalink",
916         "Zoom" : "Zoom"
917 });
918
919 OpenLayers.Lang.ro = OpenLayers.Util.extend(OpenLayers.Lang.ro, {
920         "Fixed Error" : "Eroare rezolvată",
921         "Unresolved Error" : "Eroare nerezolvată",
922         "Description" : "Descriere",
923         "Comment" : "Comentariu",
924         "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ă.",
925         "Comment/Close" : "Comentariu/Închide",
926         "Nickname" : "Nume",
927         "Add comment" : "Adaugă comentariu",
928         "Mark as fixed" : "Marchează ca rezolvată",
929         "Cancel" : "Anulează",
930         "Create OpenStreetBug" : "Crează OpenStreetBug",
931         "Create bug" : "Adaugă eroare",
932         "Bug description" : "Descrierea erorii",
933         "Create" : "Adaugă",
934         "Permalink" : "Permalink",
935         "Zoom" : "Zoom"
936 });
937 */