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