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