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