fixes CHANGES.txt
[syp.git] / js / admin.js
1 /* Copyright (c) 2009 Arnaud Renevier, Inc, published under the modified BSD
2  * license. */
3
4 /*
5  * Fix canvas rendering engine race condition. See js/syp.js for more explanation.
6  */
7 OpenLayers.Renderer.Canvas.prototype = OpenLayers.Util.extend({
8     needsRedraw: false,
9     imagesLoading: 0,
10 }, OpenLayers.Renderer.Canvas.prototype);
11 OpenLayers.Renderer.Canvas.prototype.oldRedraw = OpenLayers.Renderer.Canvas.prototype.redraw;
12 OpenLayers.Renderer.Canvas.prototype.redraw = function() {
13     if (this.imagesLoading > 0) {
14         this.needsRedraw = true;
15         return;
16     }
17     OpenLayers.Renderer.Canvas.prototype.oldRedraw.apply(this, arguments);
18 }
19 OpenLayers.Renderer.Canvas.prototype.drawExternalGraphic = function(pt, style) {
20     var img = new Image();
21     img.src = style.externalGraphic;
22        
23     if(style.graphicTitle) {
24         img.title=style.graphicTitle;           
25     }
26
27     var width = style.graphicWidth || style.graphicHeight;
28     var height = style.graphicHeight || style.graphicWidth;
29     width = width ? width : style.pointRadius*2;
30     height = height ? height : style.pointRadius*2;
31     var xOffset = (style.graphicXOffset != undefined) ?
32         style.graphicXOffset : -(0.5 * width);
33    var yOffset = (style.graphicYOffset != undefined) ?
34        style.graphicYOffset : -(0.5 * height);
35    var opacity = style.graphicOpacity || style.fillOpacity;
36        
37    var context = { img: img, 
38                    x: (pt[0]+xOffset), 
39                    y: (pt[1]+yOffset), 
40                    width: width, 
41                    height: height, 
42                    canvas: this.canvas };
43
44    var self = this;
45    this.imagesLoading++;
46    img.onerror = function() {
47        self.imagesLoading--;
48        if ((self.imagesLoading == 0) && (self.needsRedraw)) {
49            self.needsRedraw = false;
50            self.redraw();
51        }
52    }
53    img.onload = OpenLayers.Function.bind( function() {
54        self.imagesLoading--;
55        if ((self.imagesLoading == 0) && (self.needsRedraw)) {
56            self.needsRedraw = false;
57            self.redraw();
58        } else {
59             this.canvas.drawImage(this.img, this.x, 
60                              this.y, this.width, this.height);
61        }
62    }, context);   
63 }
64 // drag feature with tolerance
65 OpenLayers.Control.SypDragFeature = OpenLayers.Class (OpenLayers.Control.DragFeature, {
66     startPixel: null,
67     dragStart: null,
68     pixelTolerance : 0,
69     timeTolerance: 300,
70
71     downFeature: function(pixel) {
72         OpenLayers.Control.DragFeature.prototype.downFeature.apply(this, arguments);
73         this.dragStart = (new Date()).getTime(); 
74         this.startPixel = pixel; 
75     },
76
77     doneDragging: function(pixel) {
78         OpenLayers.Control.DragFeature.prototype.doneDragging.apply(this, arguments);
79         // Check tolerance. 
80         var passesTimeTolerance =  
81                     (new Date()).getTime() > this.dragStart + this.timeTolerance; 
82
83         var xDiff = this.startPixel.x - pixel.x; 
84         var yDiff = this.startPixel.y - pixel.y; 
85
86         var passesPixelTolerance =  
87         Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2)) > this.pixelTolerance; 
88
89         if(passesTimeTolerance && passesPixelTolerance){ 
90             this.onComplete(this.feature, pixel);    
91         } else { 
92             var feature = this.feature; 
93             var res = this.map.getResolution(); 
94             this.feature.geometry.move(res * (this.startPixel.x - this.lastPixel.x), 
95                     res * (this.lastPixel.y - this.startPixel.y)); 
96             this.layer.drawFeature(this.feature); 
97         }
98         this.layer.drawFeature(this.feature, "select");
99     },
100
101     moveFeature: function(pixel) {
102         OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this, arguments);
103         this.layer.drawFeature(this.feature, "temporary");
104     },
105
106     overFeature: function (feature) {
107         // can only drag and drop currently selected feature
108         if (feature != Admin.currentFeature) {
109             return;
110         }
111         OpenLayers.Control.DragFeature.prototype.overFeature.apply(this, arguments);
112     },
113
114     CLASS_NAME: "OpenLayers.Control.SypDragFeature"
115 });
116
117 var Admin = {
118     Markers: {
119         ICON: "media/marker-normal.png",
120         SELECT_ICON: "media/marker-selected.png",
121         TEMPORARY_ICON: "media/marker-temp.png",
122         HEIGHT: 25
123     },
124
125     map: null,
126     baseLayer: null,
127     dataLayer: null,
128     selFeatureControl: null,
129     moveFeatureControl: null,
130     addFeatureControl: null,
131
132     currentFeature: null,
133     currentFeatureLocation: null,
134
135     init: function () {
136         this.map = new OpenLayers.Map ("map", {
137                 controls:[
138                     new OpenLayers.Control.Navigation (),
139                     new OpenLayers.Control.PanZoom ()
140                 ],
141                 projection: new OpenLayers.Projection("EPSG:900913"),
142                 displayProjection: new OpenLayers.Projection("EPSG:4326")
143          });
144
145          this.baseLayer = this.createBaseLayer ();
146          this.map.addLayer(this.baseLayer);
147
148          this.map.setCenter(new OpenLayers.LonLat(0, 0), 0);
149          if (sypSettings.loggedUser) {
150             this.dataLayer = this.createDataLayer (sypSettings.loggedUser);
151             this.map.addLayer(this.dataLayer);
152             this.reset();
153          }
154     },
155
156     reset: function() {
157         this.addFeatureControl.deactivate();
158         this.moveFeatureControl.deactivate();
159         this.selFeatureControl.activate();
160         this.checkForFeatures();
161         $("#newfeature_button").show().val(SypStrings.AddItem);
162         $("#newfeature_button").unbind("click").click(function () {
163             Admin.addNewFeature();
164         });
165     },
166
167     createBaseLayer: function () {
168         return new OpenLayers.Layer.OSM("OSM");
169     },
170
171     createDataLayer: function (user) {
172         var styleMap = new OpenLayers.StyleMap (
173                         {"default": {
174                              externalGraphic: this.Markers.ICON,
175                              graphicHeight: this.Markers.HEIGHT || 32 
176                                 },
177                          "temporary": { 
178                              externalGraphic: this.Markers.TEMPORARY_ICON,
179                              graphicHeight: this.Markers.HEIGHT || 32 
180                          },
181                          "select": { 
182                              externalGraphic: this.Markers.SELECT_ICON,
183                              graphicHeight: this.Markers.HEIGHT || 32 
184                     }});
185
186         var layer = new OpenLayers.Layer.GML("KML", "items.php?from_user=" + encodeURIComponent(user),
187            {
188             styleMap: styleMap,
189             format: OpenLayers.Format.KML, 
190             projection: this.map.displayProjection,
191             eventListeners: { scope: this,
192                 loadend: this.dataLayerEndLoad
193             }
194        });
195
196         // controls
197         this.selFeatureControl = this.createSelectFeatureControl(layer)
198         this.map.addControl(this.selFeatureControl);
199         this.moveFeatureControl = this.createMoveFeatureControl(layer)
200         this.map.addControl(this.moveFeatureControl);
201         this.addFeatureControl = this.createNewfeatureControl();
202         this.map.addControl(this.addFeatureControl);
203
204         return layer;
205     },
206
207     createMoveFeatureControl: function (layer) {
208         var control = new OpenLayers.Control.SypDragFeature(
209                 layer, {
210                          });
211         return control;
212     },
213
214     createSelectFeatureControl: function (layer) {
215         var control = new OpenLayers.Control.SelectFeature(
216                 layer, {
217                         onSelect: OpenLayers.Function.bind(this.onFeatureSelect, this)
218                          });
219         return control;
220     },
221
222     createNewfeatureControl: function () {
223         var control = new OpenLayers.Control ();
224         var handler = new OpenLayers.Handler.Click(control, {
225                 'click': OpenLayers.Function.bind(FeatureMgr.add, FeatureMgr)
226             });
227         control.handler = handler;
228         return control;
229     },
230
231     onFeatureSelect: function (feature) {
232         this.showEditor(feature);
233         FeatureMgr.reset();
234         this.selFeatureControl.deactivate();
235         this.moveFeatureControl.activate();
236     },
237
238     closeEditor: function() {
239         if ($("#editor").css("display") == "none") {
240             return;
241         }
242         if (this.currentFeature && this.currentFeature.layer) {
243             this.selFeatureControl.unselect(this.currentFeature);
244         }
245         this.currentFeature = null;
246         this.currentFeatureLocation = null;
247         $("#img").removeAttr('src');
248         $("#img").parent().html($("#img").parent().html());
249         $("#img").parent().show();
250         $("#title, #description").val("");
251         $("#editor").hide();
252         // do it once before hidding and once after hidding to work in all cases
253         $("#title, #description").val(""); 
254         $("#image_file").parent().html($("#image_file").parent().html());
255         $(document).unbind("keydown");
256         this.checkForFeatures();
257         this.reset();
258     },
259
260     showEditor: function (feature) {
261         $("#newfeature_button").hide();
262         userMgr.close();
263
264         if (feature.fid) {
265             $("#delete").show();
266         } else {
267             $("#delete").hide();
268         }
269         $(document).unbind("keydown").keydown(function(e) { 
270             if (e.keyCode == 27) {
271                 Admin.cancelCurrentFeature()
272                 e.preventDefault();
273             }
274         });
275         this.currentFeature = feature;
276         this.currentFeatureLocation = new OpenLayers.Pixel(feature.geometry.x, feature.geometry.y);
277         $("#editor").show();
278         $("#instructions").text(SypStrings.DragDropHowto);
279         $("#title").val(feature.attributes.name);
280         var fullDesc = $(feature.attributes.description).parent();
281         $("#description").val(fullDesc.find('p').text());
282         var src = fullDesc.find('img').attr('src');
283         if (src) {
284             $("#img").parent().show();
285             $("#img").attr('src', src);
286             $("#image_file").parent().hide();
287             $("#image_delete").show();
288         } else {
289             $("#img").parent().hide();
290             $("#image_file").parent().show();
291             $("#image_delete").hide();
292         }
293         $("#title").select().focus(); 
294     },
295
296     dataLayerEndLoad: function() {
297         // only set zoom extent once
298         this.dataLayer.events.unregister('loadend', this, this.dataLayerEndLoad);
299         this.dataLayer.events.register('loadend', this, this.checkForFeatures);
300
301         if (!this.checkForFeatures()) {
302             return;
303         }
304
305         var map = this.map;
306         var orig = this.Utils.mbr (this.dataLayer);
307         var centerBounds = new OpenLayers.Bounds();
308
309         var mapProj = map.getProjectionObject();
310         var sypOrigProj = new OpenLayers.Projection("EPSG:4326");
311
312         var bottomLeft = new OpenLayers.LonLat(orig[0],orig[1]);
313         bottomLeft = bottomLeft.transform(sypOrigProj, mapProj);
314         var topRight = new OpenLayers.LonLat(orig[2],orig[3])
315         topRight = topRight.transform(sypOrigProj, mapProj);
316
317         centerBounds.extend(bottomLeft);
318         centerBounds.extend(topRight);
319         map.zoomToExtent(centerBounds);
320     },
321
322     checkForFeatures: function () {
323         var features = this.dataLayer.features;
324         if (features.length != 0) {
325             $("#instructions").text(SypStrings.SelectHowto);
326         }
327         return !!features.length;
328     },
329
330     addNewFeature: function () {
331         userMgr.close();
332
333         function cancel() {
334             $(document).unbind("keydown");
335             Admin.reset()
336         }
337         $(document).unbind("keydown").keydown(function(e) { 
338             if (e.keyCode == 27) {
339                 e.preventDefault();
340                 cancel();
341             }
342         });
343
344         $("#newfeature_button").val(SypStrings.Cancel);
345         $("#newfeature_button").unbind("click").click(cancel);
346
347         $("#instructions").text(SypStrings.AddHowto);
348         this.selFeatureControl.deactivate();
349         this.addFeatureControl.activate();
350         FeatureMgr.reset();
351     },
352
353     cancelCurrentFeature: function() {
354         if (AjaxMgr.running) {
355             return false;
356         }
357         var feature = this.currentFeature;
358         if (feature) {
359             if (feature.fid) {
360                 FeatureMgr.move (feature, this.currentFeatureLocation);
361             } else {
362                 this.dataLayer.removeFeatures([feature]);
363             }
364         }
365         this.closeEditor();
366         return true;
367     },
368
369     reloadLayer: function (layer) {
370         layer.destroyFeatures();
371         layer.loaded = false;
372         layer.loadGML();
373     },
374
375     Utils: {
376         /* minimum bounds rectangle containing all feature locations.
377          * FIXME: if two features are close, but separated by 180th meridian,
378          * their mbr will span the whole earth. Actually, 179° lon and -170°
379          * lon are considerated very near.
380          */
381         mbr: function (layer) {
382             var features = [];
383             var map = layer.map;
384
385             var mapProj = map.getProjectionObject();
386             var sypOrigProj = new OpenLayers.Projection("EPSG:4326");
387
388             for (var i =0; i < layer.features.length; i++) {
389                 if (layer.features[i].cluster) {
390                     features = features.concat(layer.features[i].cluster);
391                 } else {
392                     features = features.concat(layer.features);
393                 }
394             }
395
396             var minlon = 180;
397             var minlat = 88;
398             var maxlon = -180;
399             var maxlat = -88;
400
401             if (features.length == 0) {
402                 // keep default values
403             } else if (features.length == 1) {
404                 // in case there's only one feature, we show an area of at least 
405                 // 4 x 4 degrees
406                 var pos = features[0].geometry.getBounds().getCenterLonLat().clone();
407                 var lonlat = pos.transform(mapProj, sypOrigProj);
408
409                 minlon = Math.max (lonlat.lon - 2, -180);
410                 maxlon = Math.min (lonlat.lon + 2, 180);
411                 minlat = Math.max (lonlat.lat - 2, -90);
412                 maxlat = Math.min (lonlat.lat + 2, 90);
413             } else {
414                 for (var i = 0; i < features.length; i++) {
415                     var pos = features[i].geometry.getBounds().getCenterLonLat().clone();
416                     var lonlat = pos.transform(mapProj, sypOrigProj);
417                     minlon = Math.min (lonlat.lon, minlon);
418                     minlat = Math.min (lonlat.lat, minlat);
419                     maxlon = Math.max (lonlat.lon, maxlon);
420                     maxlat = Math.max (lonlat.lat, maxlat);
421                 }
422             }
423
424             return [minlon, minlat, maxlon, maxlat];
425
426         },
427
428
429         escapeHTML: function (str) {
430             if (!str) {
431                 return "";
432             }
433             return str.
434              replace(/&/gm, '&amp;').
435              replace(/'/gm, '&#39;').
436              replace(/"/gm, '&quot;').
437              replace(/>/gm, '&gt;').
438              replace(/</gm, '&lt;');
439         },
440
441         startsWith: function (str, prefix) {
442             return (str.slice(0, prefix.length) == prefix);
443         },
444
445         indexOf: function (array, item) {
446             if (array.indexOf !== undefined) {
447                 return array.indexOf(item);
448             } else {
449                 return OpenLayers.Util.indexOf(array, item);
450             }
451         }
452     }
453 }
454
455 var FeatureMgr = {
456     reset: function() {
457         this.commError("");
458     },
459
460     add: function(evt) {
461         var map = Admin.map;
462         var pos = map.getLonLatFromViewPortPx(evt.xy);
463         feature = this.update (null, pos, "", "", "");
464         Admin.addFeatureControl.deactivate();
465         Admin.selFeatureControl.select(feature);
466     },
467
468     move: function (feature, aLocation) {
469         if (!feature || !aLocation) {
470             return;
471         }
472         var curLoc = feature.geometry;
473         feature.geometry.move(aLocation.x - curLoc.x, aLocation.y - curLoc.y);
474         feature.layer.drawFeature(feature); 
475     },
476
477     update: function(feature, lonlat, imgurl, title, description) {
478         var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
479         if (!feature) {
480             feature = new OpenLayers.Feature.Vector(point);
481             Admin.dataLayer.addFeatures([feature]);
482         } else {
483             this.move (feature, point);
484         }
485         feature.attributes.name = title;
486         feature.attributes.description = "<p>" + Admin.Utils.escapeHTML(description) + "</p>"
487                                 + "<img src=\"" + imgurl + "\">"
488         return feature;
489     },
490
491     del: function (feature) {
492         var form = $("#feature_delete");
493         form.find('input[name="fid"]').val(feature.fid);
494         AjaxMgr.add({
495             form: form,
496             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
497             throbberid: "editor_throbber"
498         });
499     },
500
501     save: function (feature) {
502         var x = feature.geometry.x;
503         var y = feature.geometry.y;
504
505         var mapProj = feature.layer.map.getProjectionObject();
506         var lonlat = new OpenLayers.LonLat(x, y).
507                                     transform(mapProj,
508                                               new OpenLayers.Projection("EPSG:4326"));
509         var form = $("#feature_update");
510         form.find('input[name="lon"]').val(lonlat.lon);
511         form.find('input[name="lat"]').val(lonlat.lat);
512         form.find('input[name="fid"]').val(feature.fid);
513         form.find('input[name="keep_img"]').val(
514             $("#img").attr("src") ? "yes": "no"
515         );
516
517         if (feature.fid) {
518             form.find('input[name="request"]').val("update");
519         } else {
520             form.find('input[name="request"]').val("add");
521         }
522         AjaxMgr.add({
523             form: form,
524             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
525             throbberid: "editor_throbber"
526         });
527     },
528
529     ajaxReply: function (data) {
530         if (!data) {
531             this.commError(SypStrings.ServerError);
532             return;
533         }
534
535         var xml = new OpenLayers.Format.XML().read(data);
536         if (!xml.documentElement) {
537             this.commError(SypStrings.UnconsistentError);
538             $("title").focus();
539             return;
540         }
541
542         switch (xml.documentElement.nodeName.toLowerCase()) {
543             case "error":
544                 switch (xml.documentElement.getAttribute("reason")) {
545                     case "unauthorized":
546                         pwdMgr.reset();
547                         $("#cookie_warning").show();
548                         this.reset();
549                         Admin.cancelCurrentFeature();
550                         Admin.reset();
551                         userMgr.uninit();
552                     break;
553                     case "server":
554                         this.commError(SypStrings.ServerError);
555                         $("title").focus();
556                     break;
557                     case "unreferenced":
558                         this.commError(SypStrings.UnreferencedError);
559                         Admin.reloadLayer(Admin.dataLayer);
560                         Admin.closeEditor();
561                     break;
562                     case "nochange":
563                         this.commError(SypStrings.NochangeError);
564                         Admin.closeEditor();
565                     break;
566                     case "request":
567                         this.commError(SypStrings.RequestError);
568                         $("title").focus();
569                     break;
570                     case "toobig":
571                         this.commError(SypStrings.ToobigError);
572                         $("#image_file").parent().html($("#image_file").parent().html());
573                         $("#image_file").focus();
574                     break;
575                     case "notimage":
576                         this.commError(SypStrings.NotimageError);
577                         $("#image_file").parent().html($("#image_file").parent().html());
578                         $("#image_file").focus();
579                     break;
580                     default:
581                         this.commError(SypStrings.UnconsistentError);
582                         $("title").focus();
583                     break;
584                 }
585             break;
586             case "success":
587                 switch (xml.documentElement.getAttribute("request")) {
588                     case "del":
589                         this.commSuccess(SypStrings.DelSucces);
590                         var someFeature = false;
591                         var self = this;
592                         $.each($(xml).find("FEATURE,feature"), function () {
593                              someFeature = true;
594                              var id = parseFloat($(this).find("ID:first,id:first").text());
595                              if ((id === null) || isNaN (id)) {
596                                 return;;
597                              }
598                              var features = Admin.dataLayer.features;
599                              for (var idx = 0; idx < features.length; idx++) {
600                                  if (features[idx].fid == id) {
601                                      Admin.dataLayer.removeFeatures([features[idx]]);
602                                  }
603                              }
604                         });
605                         if (someFeature == false) {
606                             this.commError(SypStrings.UnconsistentError);
607                         } else {
608                             Admin.closeEditor();
609                         }
610                     break;
611                     case "update":
612                     case "add":
613                         var someFeature = false;
614                         var self = this;
615                         $.each($(xml).find("FEATURE,feature"), function () {
616                                 someFeature = true;
617                                 var id = parseFloat($(this).find("ID:first,id:first").text());
618                                 if ((id === null) || isNaN (id)) {
619                                     return;;
620                                 }
621
622                                 var lon = parseFloat($(this).find("LON:first,lon:first").text());
623                                 if ((typeof (lon) != "number") || isNaN (lon) ||
624                                         (lon < -180) || (lon > 180)) {
625                                     return;;
626                                 }
627
628                                 var lat = parseFloat($(this).find("LAT:first,lat:first").text());
629                                 if ((typeof (lat) != "number") || isNaN (lat) ||
630                                         (lat < -90) || (lat > 90)) {
631                                     return;;
632                                 }
633
634                                 var mapProj = Admin.map.getProjectionObject();
635                                 var lonlat = new OpenLayers.LonLat (lon, lat).
636                                                 transform( new OpenLayers.Projection("EPSG:4326"), mapProj);
637
638                                 var imgurl = $(this).find("IMGURL:first,imgurl:first").text();
639                                 var title = $(this).find("HEADING:first,heading:first").text();
640                                 var description = $(this).find("DESCRIPTION:first,description:first").text();
641
642                                 feature = self.update (Admin.currentFeature, lonlat, imgurl, title, description); 
643                                 feature.fid = id;
644                         });
645
646                         if (someFeature == false) {
647                             this.commError(SypStrings.UnconsistentError);
648                         } else {
649                             this.commSuccess(SypStrings.UpdateSucces);
650                             Admin.closeEditor();
651                         }
652
653                     break;
654                     default:
655                         this.commError(SypStrings.UnconsistentError);
656                    break;
657                 }
658             break;
659             default:
660                 this.commError(SypStrings.UnconsistentError);
661             break;
662         }
663     },
664
665     commSuccess: function (message) {
666         $("#server_comm").text(message);
667         $("#server_comm").removeClass("error success").addClass("success");
668     },
669
670     commError: function (message) {
671         $("#server_comm").text(message);
672         $("#server_comm").removeClass("error success").addClass("error");
673     }
674 }
675
676 /* maintains a queue of ajax queries, so I'm sure they all execute in the same
677  * order they were defined */
678 var AjaxMgr = {
679     _queue: [],
680
681     running: false,
682
683     add: function(query) {
684         this._queue.push(query);
685         if (this._queue.length > 1) {
686             return;
687         } else {
688             this._runQuery(query);
689         }
690     },
691
692     _runQuery: function(query) {
693         var self = this;
694         $('#api_frame').one("load", function() {
695             self.running = false;
696             self._reqEnd();
697             if (query.throbberid) {
698                 $("#" + query.throbberid).css("visibility", "hidden");
699             }
700             if (typeof (query.oncomplete) == "function") {
701                 var body = null;
702                 try {
703                     if (this.contentDocument) {
704                         body = this.contentDocument.body;
705                     } else if (this.contentWindow) {
706                         body = this.contentWindow.document.body;
707                     } else {
708                         body = document.frames[this.id].document.body;
709                     }
710                 } catch (e) {}
711                     if (body) {
712                         query.oncomplete(body.innerHTML);
713                     } else {
714                         query.oncomplete(null);
715                     }
716             }
717         });
718         query.form.attr("action", "api.php");
719         query.form.attr("target", "api_frame");
720         query.form.attr("method", "post");
721         this.running = true;
722         query.form.get(0).submit();
723         if (query.throbberid) {
724             $("#" + query.throbberid).css("visibility", "visible");
725         }
726         if (typeof (query.onsend) == "function") {
727             query.onsend();
728         }
729     },
730
731     _reqEnd: function() {
732         this._queue.shift();
733         if (this._queue.length > 0) {
734             this._reqEnd(this._queue[0]);
735         }
736     }
737 }
738
739 var pwdMgr = {
740
741     init: function () {
742         $("#login_form").submit(this.submit);
743         $("#user").focus().select();
744     },
745
746     reset: function() {
747         this.commError ("");
748         $("#login_area").show();
749         $("#password").val("");
750         $("#user").val(sypSettings.loggedUser).focus().select();
751     },
752
753     submit: function () {
754         try {
755             pwdMgr.commError("");
756             var req = {
757                 form:  $("#login_form"),
758                 throbberid: "pwd_throbber",
759                 onsend: function() {
760                     $("#login_error").hide();
761
762                     // we need a timeout; otherwise those fields will not be submitted
763                     window.setTimeout(function() { 
764                             // removes focus from #password before disabling it. Otherwise, opera
765                             // prevents re-focusing it after re-enabling it.
766                             $("#user, #password").blur(); 
767                             $("#login_submit, #user, #password").attr("disabled", "disabled");
768                     }, 0)
769                 },
770                 oncomplete: OpenLayers.Function.bind(pwdMgr.ajaxReply, pwdMgr)
771             };
772             AjaxMgr.add(req);
773         } catch(e) {}
774         return false;
775     },
776
777     ajaxReply: function (data) {
778         // here, we need a timeout because onsend timeout sometimes has not been triggered yet
779         window.setTimeout(function() {
780             $("#login_submit, #user, #password").removeAttr("disabled");
781         }, 0);
782
783         if (!data) {
784             this.commError(SypStrings.ServerError);
785             $("#login_error").show();
786             window.setTimeout(function() {
787                     $("#user").focus().select();
788             }, 0);
789             return;
790         }
791
792         var xml = new OpenLayers.Format.XML().read(data);
793         if (!xml.documentElement) {
794             this.commError(SypStrings.UnconsistentError);
795             $("#login_error").show();
796             window.setTimeout(function() {
797                     $("#user").focus().select();
798             }, 0);
799         }
800
801         switch (xml.documentElement.nodeName.toLowerCase()) {
802             case "error":
803                 switch (xml.documentElement.getAttribute("reason")) {
804                     case "server":
805                         this.commError(SypStrings.ServerError);
806                     break;
807                     case "unauthorized":
808                         this.commError(SypStrings.UnauthorizedError);
809                     break;
810                     case "request":
811                         this.commError(SypStrings.RequestError);
812                     break;
813                     default:
814                         this.commError(SypStrings.UnconsistentError);
815                     break;
816                 }
817                 $("#login_error").show();
818                 window.setTimeout(function() {
819                         $("#user").focus().select();
820                 }, 0);
821             break;
822             case "success":
823                 $("#login_area").hide();
824
825                 user = $(xml).find("USER,user").text();
826                 sypSettings.loggedUser = user;
827
828                 if (sypSettings.loggedUser == "admin")  {
829                     userMgr.init();
830                 }
831
832                 if (Admin.selFeatureControl) {
833                     Admin.selFeatureControl.destroy();
834                 }
835                 if (Admin.moveFeatureControl) {
836                     Admin.moveFeatureControl.destroy();
837                 }
838                 if (Admin.addFeatureControl) {
839                     Admin.addFeatureControl.destroy();
840                 }
841                 if (Admin.dataLayer) {
842                     Admin.dataLayer.destroy();
843                 }
844
845                 Admin.dataLayer = Admin.createDataLayer(user);
846                 Admin.map.addLayer(Admin.dataLayer);
847                 Admin.reset();
848
849             break;
850             default:
851                 this.commError(SypStrings.UnconsistentError);
852             break;
853         }
854     },
855
856     commError: function (message) {
857         $("#login_error").text(message);
858         if (message) {
859             $("#login_error").show();
860         } else {
861             $("#login_error").hide();
862         }
863     }
864 }
865
866 var userMgr = {
867     _adduserDisplayed: false,
868     _changepassDisplayed: false,
869
870     init: function() {
871         $("#user_close").unbind("click").click(function () {
872             userMgr.close()
873         });
874
875         $("#change_pass").unbind("click").click(function() {
876             userMgr.toggleChangePass();
877             return false;
878         });
879         $("#changepass").unbind("submit").submit(function() {
880             try {
881                 userMgr.changepass();
882             } catch(e) {}
883             return false;
884         });
885
886         if (sypSettings.loggedUser != "admin") {
887             return;
888         }
889
890         $("#add_user").show();
891         $("#add_user").unbind("click").click(function () {
892             userMgr.toggleAddUser();
893             return false;
894         });
895         $("#newuser").unbind("submit").submit(function() {
896             try {
897                 userMgr.add();
898             } catch(e) {}
899             return false;
900         });
901
902     },
903
904     disableForms: function() {
905         $("#newuser_name, #newuser_password, #newuser_password_confirm, #newuser_submit").attr("disabled", "disabled");
906         $("#pass_current, #pass_new, #pass_new_confirm, #pass_submit").attr("disabled", "disabled");
907     },
908
909     enableForms: function() {
910         $("#newuser_name, #newuser_password, #newuser_password_confirm, #newuser_submit").removeAttr("disabled");
911         $("#pass_current, #pass_new, #pass_new_confirm, #pass_submit").removeAttr("disabled");
912     },
913
914     resetForms: function() {
915         $("#newuser_name, #newuser_password, #newuser_password_confirm").val("");
916         $("#pass_current, #pass_new, #pass_new_confirm").val("");
917     },
918
919     uninit: function() {
920         this.close();
921         $("#add_user").unbind("click");
922         $("#add_user").hide();
923         $("#change_pass").unbind("click");
924         $("#user_close").unbind("click");
925         $("#newuser").unbind("submit");
926         $("#changepass").unbind("submit");
927     },
928
929     close: function() {
930         this.closeChangePass();
931         this.closeAddUser();
932     },
933
934     toggleChangePass: function() {
935         if (this._changepassDisplayed) {
936             this.closeChangePass();
937         } else {
938             this.showChangePass();
939         }
940     },
941
942     showChangePass: function() {
943         if (!Admin.cancelCurrentFeature()) {
944             return;
945         }
946         this.closeAddUser();
947
948         $(document).unbind("keydown").keydown(function(e) { 
949             if (e.keyCode == 27) {
950                 userMgr.closeChangePass()
951                 e.preventDefault();
952             }
953         });
954
955         this.resetForms();
956         this.enableForms();
957         $("#user_area, #changepass").show();
958         this.commError("");
959
960         // XXX: setTimeout needed because otherwise, map becomes hidden in IE. Why ??
961         window.setTimeout(function() { 
962             $("#pass_current").focus();
963         }, 0);
964
965         this._changepassDisplayed = true;
966     },
967
968     closeChangePass: function() {
969         if (!this._changepassDisplayed) {
970             return;
971         }
972         $("#user_area, #changepass").hide();
973         $(document).unbind("keydown");
974         this._changepassDisplayed = false;
975     },
976
977     changepass: function() {
978         var newpass = $("#pass_new").val();
979         var newpass_confirm = $("#pass_new_confirm").val();
980         if (newpass != newpass_confirm) {
981             this.commError(SypStrings.userPasswordmatchError);
982             $("#pass_new").focus().select();
983             return;
984         }
985
986         if (!newpass) {
987             this.commError(SypStrings.emptyPasswordError);
988             $("#pass_new").focus().select();
989             return;
990         }
991
992         var curpass = $("#pass_current").val();
993         if (newpass == curpass) {
994             this.commError(SypStrings.changeSamePass);
995             $("#pass_new").focus().select();
996             return;
997         }
998
999         this.commError("");
1000
1001         AjaxMgr.add({
1002             form: $("#changepass"),
1003             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
1004             throbberid: "user_throbber",
1005             onsend: function() { 
1006                 // we need a timeout; otherwise those fields will not be submitted
1007                 window.setTimeout(function() {
1008                     // removes focus from #password before disabling it. Otherwise, opera
1009                     // prevents re-focusing it after re-enabling it.
1010                     $("#pass_current, #pass_new, #pass_new_confirm").blur(); 
1011                     userMgr.disableForms();
1012                 }, 0);
1013             }
1014         });
1015     },
1016
1017     toggleAddUser: function() {
1018         if (this._adduserDisplayed) {
1019             this.closeAddUser();
1020         } else {
1021             this.showAddUser();
1022         }
1023     },
1024
1025     showAddUser: function() {
1026         if (!Admin.cancelCurrentFeature()) {
1027             return;
1028         }
1029
1030         this.closeChangePass();
1031
1032         $(document).unbind("keydown").keydown(function(e) { 
1033             if (e.keyCode == 27) {
1034                 userMgr.closeAddUser()
1035                 e.preventDefault();
1036             }
1037         });
1038
1039         $("#user_area, #newuser").show();
1040         this.resetForms();
1041         this.enableForms();
1042         this.commError("");
1043
1044         // XXX: setTimeout needed because otherwise, map becomes hidden in IE. Why ??
1045         window.setTimeout(function() { 
1046             $("#newuser_name").focus();
1047         }, 0);
1048
1049         this._adduserDisplayed = true;
1050     },
1051
1052     closeAddUser: function() {
1053         if (!this._adduserDisplayed) {
1054             return;
1055         }
1056         $("#user_area, #newuser").hide();
1057         $(document).unbind("keydown");
1058         this._adduserDisplayed = false;
1059     },
1060
1061     add: function() {
1062         var newuser_name = $("#newuser_name").val();
1063         if (!newuser_name) {
1064             this.commError(SypStrings.newUserNonameError);
1065             $("#newuser_name").focus();
1066             return;
1067         }
1068
1069         var newuser_pass = $("#newuser_password").val();
1070         var newuser_pass_confirm = $("#newuser_password_confirm").val();
1071         if (newuser_pass != newuser_pass_confirm) {
1072             this.commError(SypStrings.userPasswordmatchError);
1073             $("#newuser_password").focus().select();
1074             return;
1075         }
1076
1077         if (!newuser_pass) {
1078             this.commError(SypStrings.emptyPasswordError);
1079             $("#pass_new").focus().select();
1080             return;
1081         }
1082
1083         this.commError("");
1084
1085         AjaxMgr.add({
1086             form: $("#newuser"),
1087             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
1088             throbberid: "user_throbber",
1089             onsend: function() { 
1090                 // we need a timeout; otherwise those fields will not be submitted
1091                 window.setTimeout(function() {
1092                     // removes focus from #password before disabling it. Otherwise, opera
1093                     // prevents re-focusing it after re-enabling it.
1094                     $("#newuser_name, #newuser_password, #newuser_password_confirm").blur(); 
1095                     userMgr.disableForms();
1096                 }, 0);
1097             }
1098         });
1099     },
1100
1101     ajaxReply: function (data) {
1102         if (!data) {
1103             // here, we need a timeout because onsend timeout sometimes has not been triggered yet
1104             var self = this;
1105             window.setTimeout(function() {
1106                 self.enableForms();
1107              }, 0);
1108             this.commError(SypStrings.ServerError);
1109             return;
1110         }
1111
1112         var xml = new OpenLayers.Format.XML().read(data);
1113         if (!xml.documentElement) {
1114             // here, we need a timeout because onsend timeout sometimes has not been triggered yet
1115             var self = this;
1116             window.setTimeout(function() {
1117                 self.enableForms();
1118              }, 0);
1119             this.commError(SypStrings.UnconsistentError);
1120             return;
1121         }
1122
1123         var needFormEnabling = true;
1124         var focusEl = null;
1125
1126         switch (xml.documentElement.nodeName.toLowerCase()) {
1127             case "error":
1128                 switch (xml.documentElement.getAttribute("reason")) {
1129                     case "unauthorized":
1130                         pwdMgr.reset();
1131                         $("#cookie_warning").show();
1132                         Admin.reset();
1133                         this.uninit();
1134                     break;
1135                     case "server":
1136                         this.commError(SypStrings.ServerError);
1137                         if (this._adduserDisplayed) {
1138                             focusEl = $("#newuser_name");
1139                         } else if (this._changepassDisplayed) {
1140                             focusEl = $("#pass_current");
1141                         }
1142                     break;
1143                     case "request":
1144                         this.commError(SypStrings.RequestError);
1145                         if (this._adduserDisplayed) {
1146                             focusEl = $("#newuser_name");
1147                         } else if (this._changepassDisplayed) {
1148                             focusEl = $("#pass_current");
1149                         }
1150                     break;
1151                     case "wrongpass":
1152                         this.commError(SypStrings.changePassBadPass);
1153                         focusEl = $("#pass_current");
1154                     break;
1155                     case "newuser_exists":
1156                         this.commError(SypStrings.newUserExistsError);
1157                         focusEl = $("#newuser_name");
1158                     break;
1159                     default:
1160                         this.commError(SypStrings.UnconsistentError);
1161                         if (this._adduserDisplayed) {
1162                             focusEl = $("#newuser_name");
1163                         } else if (this._changepassDisplayed) {
1164                             focusEl = $("#pass_current");
1165                         }
1166                     break;
1167                 }
1168             break;
1169             case "success":
1170                 switch (xml.documentElement.getAttribute("request")) {
1171                     case "newuser":
1172                         this.commSuccess(SypStrings.newUserSuccess);
1173                         needFormEnabling = false;
1174                     break;
1175                     case "changepass":
1176                         this.commSuccess(SypStrings.changePassSuccess);
1177                         needFormEnabling = false;
1178                     break;
1179                     default:
1180                         this.commError(SypStrings.UnconsistentError);
1181                         focusEl = $("newuser_name");
1182                     break;
1183                 }
1184             break;
1185             default:
1186                 this.commError(SypStrings.UnconsistentError);
1187                 focusEl = $("newuser_name");
1188             break;
1189         }
1190
1191         if (needFormEnabling) {
1192             // here, we need a timeout because onsend timeout sometimes has not been triggered yet
1193             var self = this;
1194             window.setTimeout(function() {
1195                 self.enableForms();
1196                 if (focusEl) {
1197                     focusEl.select().focus();
1198                 }
1199              }, 0);
1200         } else {
1201             if (focusEl) {
1202                 focusEl.focus().select();
1203             }
1204         }
1205
1206     },
1207
1208     commSuccess: function (message) {
1209         $("#user_comm").text(message);
1210         $("#user_comm").removeClass("error success").addClass("success");
1211     },
1212
1213     commError: function (message) {
1214         $("#user_comm").text(message);
1215         $("#user_comm").removeClass("error success").addClass("error");
1216     }
1217 }
1218
1219 $(window).load(function () {
1220     // if using .ready, ie triggers an error when trying to access
1221     // document.namespaces
1222     pwdMgr.init();
1223     $("#newfeature_button").click(function () {
1224         Admin.addNewFeature();
1225     });
1226     $("#editor_close").click(function () {
1227         Admin.cancelCurrentFeature()
1228     });
1229     $("#feature_update").submit(function() {
1230         try {
1231             FeatureMgr.save(Admin.currentFeature);
1232         } catch(e) {}
1233         return false;
1234     });
1235     $("#feature_delete").submit(function() {
1236         try {
1237             FeatureMgr.del(Admin.currentFeature);
1238         } catch(e) {}
1239         return false;
1240     });
1241     $("#image_delete").click(function() {
1242             $("#img").removeAttr('src');
1243             // needs to rebuild element otherwise some browsers still
1244             // display image.
1245             $("#img").parent().html($("#img").parent().html());
1246             $("#img").parent().hide();
1247             $("#image_delete").hide();
1248             $("#image_file").parent().show();
1249     });
1250
1251     userMgr.init();
1252     Admin.init();
1253 });