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