]> dev.renevier.net Git - syp.git/blob - js/admin.js
forbird creation or deletion of item; forbids adding or deleting an image
[syp.git] / js / admin.js
1 /* Copyright (c) 2009 Arnaud Renevier, Inc, published under the modified BSD
2  * license. */
3
4 // drag feature with tolerance
5 OpenLayers.Control.SypDragFeature = OpenLayers.Class (OpenLayers.Control.DragFeature, {
6     startPixel: null,
7     dragStart: null,
8     pixelTolerance : 0,
9     timeTolerance: 300,
10
11     downFeature: function(pixel) {
12         OpenLayers.Control.DragFeature.prototype.downFeature.apply(this, arguments);
13         this.dragStart = (new Date()).getTime(); 
14         this.startPixel = pixel; 
15     },
16
17     doneDragging: function(pixel) {
18         OpenLayers.Control.DragFeature.prototype.doneDragging.apply(this, arguments);
19         // Check tolerance. 
20         var passesTimeTolerance =  
21                     (new Date()).getTime() > this.dragStart + this.timeTolerance; 
22
23         var xDiff = this.startPixel.x - pixel.x; 
24         var yDiff = this.startPixel.y - pixel.y; 
25
26         var passesPixelTolerance =  
27         Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2)) > this.pixelTolerance; 
28
29         if(passesTimeTolerance && passesPixelTolerance){ 
30             this.onComplete(this.feature, pixel);    
31         } else { 
32             var feature = this.feature; 
33             var res = this.map.getResolution(); 
34             this.feature.geometry.move(res * (this.startPixel.x - this.lastPixel.x), 
35                     res * (this.lastPixel.y - this.startPixel.y)); 
36             this.layer.drawFeature(this.feature); 
37         }
38         this.layer.drawFeature(this.feature, "select");
39     },
40
41     moveFeature: function(pixel) {
42         OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this, arguments);
43         this.layer.drawFeature(this.feature, "temporary");
44     },
45
46     overFeature: function (feature) {
47         // can only drag and drop currently selected feature
48         if (feature != Admin.currentFeature) {
49             return;
50         }
51         OpenLayers.Control.DragFeature.prototype.overFeature.apply(this, arguments);
52     },
53
54     CLASS_NAME: "OpenLayers.Control.SypDragFeature"
55 });
56
57 var Admin = {
58     Settings: {
59         MARKER_ICON: "openlayers/img/marker-blue.png",
60         MARKER_ICON_HEIGHT: 25,
61         MARKER_SELECT_ICON: "openlayers/img/marker-green.png",
62         MARKER_SELECT_ICON_HEIGHT: 25,
63         MARKER_TEMPORARY_ICON: "openlayers/img/marker-gold.png",
64         MARKER_TEMPORARY_ICON_HEIGHT: 25
65     },
66
67     map: null,
68     baseLayer: null,
69     dataLayer: null,
70     selFeatureControl: null,
71     moveFeatureControl: null,
72     addFeatureControl: null,
73
74     currentFeature: null,
75     currentFeatureLocation: null,
76
77     init: function () {
78         this.map = new OpenLayers.Map ("map", {
79                 controls:[
80                     new OpenLayers.Control.Navigation (),
81                     new OpenLayers.Control.PanZoom ()
82                 ],
83                 projection: new OpenLayers.Projection("EPSG:900913"),
84                 displayProjection: new OpenLayers.Projection("EPSG:4326")
85          });
86
87          this.baseLayer = this.createBaseLayer ();
88          this.dataLayer = this.createDataLayer ();
89          this.map.addLayers([this.baseLayer, this.dataLayer]);
90
91          // controls
92          this.selFeatureControl = this.createSelectFeatureControl();
93          this.map.addControl(this.selFeatureControl);
94          this.moveFeatureControl = this.createMoveFeatureControl();
95          this.map.addControl(this.moveFeatureControl);
96          this.addFeatureControl = this.createNewfeatureControl();
97          this.map.addControl(this.addFeatureControl);
98
99          // position
100          var centerBounds = new OpenLayers.Bounds();
101
102          var mapProj = this.map.getProjectionObject();
103          var sypOrigProj = new OpenLayers.Projection("EPSG:4326");
104
105          var bottomLeft = new OpenLayers.LonLat(sypOrig[0],sypOrig[1]);
106          bottomLeft = bottomLeft.transform(sypOrigProj, mapProj);
107          var topRight = new OpenLayers.LonLat(sypOrig[2],sypOrig[3])
108          topRight = topRight.transform(sypOrigProj, mapProj);
109
110          centerBounds.extend(bottomLeft);
111          centerBounds.extend(topRight);
112
113          // at that moment, ie does not know size of the map, we need to update
114          // manually
115          this.map.updateSize();
116          this.map.zoomToExtent(centerBounds);
117
118          this.reset();
119     },
120
121     reset: function() {
122         this.addFeatureControl.deactivate();
123         this.moveFeatureControl.deactivate();
124         this.selFeatureControl.activate();
125         this.checkForFeatures();
126         $("#newfeature_button").show().val(SypStrings.AddItem);
127         $("#newfeature_button").unbind("click").click(function () {
128             Admin.addNewFeature();
129         });
130     },
131
132     createBaseLayer: function () {
133         return new OpenLayers.Layer.OSM("OSM");
134     },
135
136     createDataLayer: function () {
137         var styleMap = new OpenLayers.StyleMap (
138                         {"default": {
139                              externalGraphic: this.Settings.MARKER_ICON,
140                              graphicHeight: this.Settings.MARKER_ICON_HEIGHT
141                                                   || 32 
142                                 },
143                          "temporary": { 
144                              externalGraphic: this.Settings.MARKER_TEMPORARY_ICON,
145                              graphicHeight: this.Settings.MARKER_TEMPORARY_ICON_HEIGHT
146                                                   || 32 
147                          },
148                          "select": { 
149                              externalGraphic: this.Settings.MARKER_SELECT_ICON,
150                              graphicHeight: this.Settings.MARKER_SELECT_ICON_HEIGHT
151                                                   || 32 
152                     }});
153
154         var layer = new OpenLayers.Layer.GML("KML", "items.php", 
155            {
156             styleMap: styleMap,
157             format: OpenLayers.Format.KML, 
158             projection: this.map.displayProjection,
159             eventListeners: { scope: this,
160                 loadend: this.checkForFeatures
161             }
162        });
163
164         return layer;
165     },
166
167     createMoveFeatureControl: function () {
168         var control = new OpenLayers.Control.SypDragFeature(
169                 this.dataLayer, {
170                          });
171         return control;
172     },
173
174     createSelectFeatureControl: function () {
175         var control = new OpenLayers.Control.SelectFeature(
176                 this.dataLayer, {
177                         onSelect: OpenLayers.Function.bind(this.onFeatureSelect, this)
178                          });
179         return control;
180     },
181
182     createNewfeatureControl: function () {
183         var control = new OpenLayers.Control ();
184         var handler = new OpenLayers.Handler.Click(control, {
185                 'click': OpenLayers.Function.bind(FeatureMgr.add, FeatureMgr)
186             });
187         control.handler = handler;
188         return control;
189     },
190
191     onFeatureSelect: function (feature) {
192         this.showEditor(feature);
193         FeatureMgr.reset();
194         this.selFeatureControl.deactivate();
195         this.moveFeatureControl.activate();
196     },
197
198     closeEditor: function() {
199         if (this.currentFeature && this.currentFeature.layer) {
200             this.selFeatureControl.unselect(this.currentFeature);
201         }
202         this.currentFeature = null;
203         this.currentFeatureLocation = null;
204         $("#img").removeAttr('src');
205         $("#img").parent().html($("#img").parent().html());
206         $("#img").parent().show();
207         $("#title, #description").val("");
208         $("#editor").hide();
209         // do it once before hidding and once after hidding to work in all cases
210         $("#title, #description").val(""); 
211         $("#image_file").parent().html($("#image_file").parent().html());
212         $(document).unbind("keydown");
213         this.checkForFeatures();
214         this.reset();
215     },
216
217     showEditor: function (feature) {
218         $("#newfeature_button").hide();
219         if (feature.fid) {
220             $("#delete").show();
221         } else {
222             $("#delete").hide();
223         }
224         $(document).unbind("keydown").keydown(function(e) { 
225             if (e.keyCode == 27) {
226                 Admin.cancelCurrentFeature()
227                 e.preventDefault();
228             }
229         });
230         this.currentFeature = feature;
231         this.currentFeatureLocation = new OpenLayers.Pixel(feature.geometry.x, feature.geometry.y);
232         $("#editor").show();
233         $("#instructions").text(SypStrings.DragDropHowto);
234         $("#title").val(feature.attributes.name);
235         var fullDesc = $(feature.attributes.description).parent();
236         $("#description").val(fullDesc.find('p').text());
237         var src = fullDesc.find('img').attr('src');
238         if (src) {
239             $("#img").parent().show();
240             $("#img").attr('src', src);
241             $("#image_file").parent().hide();
242             $("#image_delete").show();
243         } else {
244             $("#img").parent().hide();
245             $("#image_file").parent().show();
246             $("#image_delete").hide();
247         }
248         $("#title").select().focus(); 
249     },
250
251     checkForFeatures: function () {
252         if (this.dataLayer.features.length != 0) {
253             $("#instructions").text(SypStrings.SelectHowto);
254         }
255     },
256
257     addNewFeature: function () {
258         alert (SypStrings.DisabledForDemo);
259         return;
260         function cancel() {
261             $(document).unbind("keydown");
262             Admin.reset()
263         }
264         $(document).unbind("keydown").keydown(function(e) { 
265             if (e.keyCode == 27) {
266                 e.preventDefault();
267                 cancel();
268             }
269         });
270
271         $("#newfeature_button").val("annuler");
272         $("#newfeature_button").unbind("click").click(cancel);
273
274         $("#instructions").text(SypStrings.AddHowto);
275         this.selFeatureControl.deactivate();
276         this.addFeatureControl.activate();
277         FeatureMgr.reset();
278     },
279
280     cancelCurrentFeature: function() {
281         if (AjaxMgr.running) {
282             return;
283         }
284         var feature = this.currentFeature;
285         if (feature.fid) {
286             FeatureMgr.move (feature, this.currentFeatureLocation);
287         } else {
288             this.dataLayer.removeFeatures([feature]);
289         }
290         this.closeEditor();
291     },
292
293     reloadLayer: function (layer) {
294         layer.destroyFeatures();
295         layer.loaded = false;
296         layer.loadGML();
297     },
298
299     Utils: {
300         escapeHTML: function (str) {
301             if (!str) {
302                 return "";
303             }
304             return str.
305              replace(/&/gm, '&').
306              replace(/'/gm, ''').
307              replace(/"/gm, '"').
308              replace(/>/gm, '>').
309              replace(/</gm, '&lt;');
310         },
311
312         startsWith: function (str, prefix) {
313             return (str.slice(0, prefix.length) == prefix);
314         },
315
316         indexOf: function (array, item) {
317             if (array.indexOf !== undefined) {
318                 return array.indexOf(item);
319             } else {
320                 return OpenLayers.Util.indexOf(array, item);
321             }
322         }
323     }
324 }
325
326 var FeatureMgr = {
327     reset: function() {
328         this.commError("");
329     },
330
331     add: function(evt) {
332         var map = Admin.map;
333         var pos = map.getLonLatFromViewPortPx(evt.xy);
334         feature = this.update (null, pos, "", "", "");
335         Admin.addFeatureControl.deactivate();
336         Admin.selFeatureControl.select(feature);
337     },
338
339     move: function (feature, aLocation) {
340         if (!feature || !aLocation) {
341             return;
342         }
343         var curLoc = feature.geometry;
344         feature.geometry.move(aLocation.x - curLoc.x, aLocation.y - curLoc.y);
345         feature.layer.drawFeature(feature); 
346     },
347
348     update: function(feature, lonlat, imgurl, title, description) {
349         var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
350         if (!feature) {
351             feature = new OpenLayers.Feature.Vector(point);
352             Admin.dataLayer.addFeatures([feature]);
353         } else {
354             this.move (feature, point);
355         }
356         feature.attributes.name = title;
357         feature.attributes.description = "<p>" + Admin.Utils.escapeHTML(description) + "</p>"
358                                 + "<img src=\"" + imgurl + "\">"
359         return feature;
360     },
361
362     del: function (feature) {
363         alert (SypStrings.DisabledForDemo);
364         return;
365         var form = $("#feature_delete");
366         form.find('input[name="fid"]').val(feature.fid);
367         AjaxMgr.add({
368             form: form,
369             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this)
370         });
371     },
372
373     save: function (feature) {
374         var x = feature.geometry.x;
375         var y = feature.geometry.y;
376
377         var mapProj = feature.layer.map.getProjectionObject();
378         var lonlat = new OpenLayers.LonLat(x, y).
379                                     transform(mapProj,
380                                               new OpenLayers.Projection("EPSG:4326"));
381         var form = $("#feature_update");
382         form.find('input[name="lon"]').val(lonlat.lon);
383         form.find('input[name="lat"]').val(lonlat.lat);
384         form.find('input[name="fid"]').val(feature.fid);
385         form.find('input[name="keep_img"]').val(
386             $("#img").attr("src") ? "yes": "no"
387         );
388
389         if (feature.fid) {
390             form.find('input[name="request"]').val("update");
391         } else {
392             form.find('input[name="request"]').val("add");
393         }
394         AjaxMgr.add({
395             form: form,
396             oncomplete: OpenLayers.Function.bind(this.ajaxReply, this)
397         });
398     },
399
400     ajaxReply: function (data) {
401         if (!data) {
402             this.commError(SypStrings.ServerError);
403             return;
404         }
405
406         var xml = new OpenLayers.Format.XML().read(data);
407
408         switch (xml.documentElement.nodeName.toLowerCase()) {
409             case "error":
410                 switch (xml.documentElement.getAttribute("reason")) {
411                     case "unauthorized":
412                         $("#login_area").show();
413                         $("#cookie_warning").show();
414                         this.reset();
415                         Admin.cancelCurrentFeature();
416                         Admin.reset();
417                     break;
418                     case "server":
419                         this.commError(SypStrings.ServerError);
420                         $("title").focus();
421                     break;
422                     case "unreferenced":
423                         this.commError(SypStrings.UnreferencedError);
424                         Admin.reloadLayer(Admin.dataLayer);
425                         Admin.closeEditor();
426                     break;
427                     case "nochange":
428                         this.commError(SypStrings.NochangeError);
429                         Admin.closeEditor();
430                     break;
431                     case "request":
432                         this.commError(SypStrings.RequestError);
433                         $("title").focus();
434                     break;
435                     case "toobig":
436                         this.commError(SypStrings.ToobigError);
437                         $("#image_file").parent().html($("#image_file").parent().html());
438                         $("#image_file").focus();
439                     break;
440                     case "notimage":
441                         this.commError(SypStrings.NotimageError);
442                         $("#image_file").parent().html($("#image_file").parent().html());
443                         $("#image_file").focus();
444                     break;
445                     default:
446                         this.commError(SypStrings.UnknownError);
447                         $("title").focus();
448                     break;
449                 }
450             break;
451             case "success":
452                 switch (xml.documentElement.getAttribute("request")) {
453                     case "del":
454                         this.commSuccess(SypStrings.DelSucces);
455                         var someFeature = false;
456                         var self = this;
457                         $.each($(xml).find("FEATURE,feature"), function () {
458                              someFeature = true;
459                              var id = parseFloat($(this).find("ID:first,id:first").text());
460                              if ((id === null) || isNaN (id)) {
461                                 return;;
462                              }
463                              var features = Admin.dataLayer.features;
464                              for (var idx = 0; idx < features.length; idx++) {
465                                  if (features[idx].fid == id) {
466                                      Admin.dataLayer.removeFeatures([features[idx]]);
467                                  }
468                              }
469                         });
470                         if (someFeature == false) {
471                             this.commError(SypStrings.UnconsistentError);
472                         } else {
473                             Admin.closeEditor();
474                         }
475                     break;
476                     case "update":
477                     case "add":
478                         var someFeature = false;
479                         var self = this;
480                         $.each($(xml).find("FEATURE,feature"), function () {
481                                 someFeature = true;
482                                 var id = parseFloat($(this).find("ID:first,id:first").text());
483                                 if ((id === null) || isNaN (id)) {
484                                     return;;
485                                 }
486
487                                 var lon = parseFloat($(this).find("LON:first,lon:first").text());
488                                 if ((typeof (lon) != "number") || isNaN (lon) ||
489                                         (lon < -180) || (lon > 180)) {
490                                     return;;
491                                 }
492
493                                 var lat = parseFloat($(this).find("LAT:first,lat:first").text());
494                                 if ((typeof (lat) != "number") || isNaN (lat) ||
495                                         (lat < -90) || (lat > 90)) {
496                                     return;;
497                                 }
498
499                                 var mapProj = Admin.map.getProjectionObject();
500                                 var lonlat = new OpenLayers.LonLat (lon, lat).
501                                                 transform( new OpenLayers.Projection("EPSG:4326"), mapProj);
502
503                                 var imgurl = $(this).find("IMGURL:first,imgurl:first").text();
504                                 var title = $(this).find("HEADING:first,heading:first").text();
505                                 var description = $(this).find("DESCRIPTION:first,description:first").text();
506
507                                 feature = self.update (Admin.currentFeature, lonlat, imgurl, title, description); 
508                                 feature.fid = id;
509                         });
510
511                         if (someFeature == false) {
512                             this.commError(SypStrings.UnconsistentError);
513                         } else {
514                             this.commSuccess(SypStrings.UpdateSucces);
515                             Admin.closeEditor();
516                         }
517
518                     break;
519                     default:
520                         this.commError(SypStrings.UnconsistentError);
521                    break;
522                 }
523             break;
524             default:
525                 this.commError(SypStrings.UnconsistentError);
526             break;
527         }
528     },
529
530     commSuccess: function (message) {
531         $("#server_comm").text(message);
532         $("#server_comm").removeClass().addClass("success");
533     },
534
535     commError: function (message) {
536         $("#server_comm").text(message);
537         $("#server_comm").removeClass().addClass("error");
538     }
539 }
540
541 /* maintains a queue of ajax queries, so I'm sure they all execute in the same
542  * order they were defined */
543 var AjaxMgr = {
544     _queue: [],
545
546     running: false,
547
548     add: function(query) {
549         this._queue.push(query);
550         if (this._queue.length > 1) {
551             return;
552         } else {
553             this._runQuery(query);
554         }
555     },
556
557     _runQuery: function(query) {
558         var self = this;
559         $('#api_frame').one("load", function() {
560             self.running = false;
561             self._reqEnd();
562             if (typeof (query.oncomplete) == "function") {
563                 var body = null;
564                 try {
565                     if (this.contentDocument) {
566                         body = this.contentDocument.body;
567                     } else if (this.contentWindow) {
568                         body = this.contentWindow.document.body;
569                     } else {
570                         body = document.frames[this.id].document.body;
571                     }
572                 } catch (e) {}
573                     if (body) {
574                         query.oncomplete(body.innerHTML);
575                     } else {
576                         query.oncomplete(null);
577                     }
578             }
579         });
580         query.form.attr("action", "api.php");
581         query.form.attr("target", "api_frame");
582         query.form.attr("method", "post");
583         this.running = true;
584         query.form.get(0).submit();
585         if (typeof (query.onsend) == "function") {
586             query.onsend();
587         }
588     },
589
590     _reqEnd: function() {
591         this._queue.shift();
592         if (this._queue.length > 0) {
593             this._reqEnd(this._queue[0]);
594         }
595     }
596 }
597
598 var pwdMgr = {
599
600     init: function () {
601         $("#login_form").submit(this.submit);
602         $("#password").focus().select();
603     },
604
605     reset: function() {
606         this.commError ("");
607     },
608
609     submit: function () {
610         try {
611             pwdMgr.commError("");
612             var req = {
613                 form:  $("#login_form"),
614                 onsend: function() {
615                     $("#pwd_throbber").css("visibility", "visible");
616                     $("#login_error").hide();
617
618                     // we need a timeout; otherwise those fields will not be submitted
619                     window.setTimeout(function() { 
620                             // removes focus from #password before disabling it. Otherwise, opera
621                             // prevents re-focusing it after re-enabling it.
622                             $("#password").blur(); 
623                             $("#login_submit, #password").attr("disabled", "disabled");
624                     }, 0)
625                 },
626                 oncomplete: OpenLayers.Function.bind(pwdMgr.ajaxReply, pwdMgr)
627             };
628             AjaxMgr.add(req);
629         } catch(e) {}
630         return false;
631     },
632
633     ajaxReply: function (data) {
634
635         $("#pwd_throbber").css("visibility", "hidden");
636         // here, we need a timeout because onsend timeout sometimes has not been triggered yet
637         window.setTimeout(function() {
638             $("#login_submit, #password").removeAttr("disabled");
639         }, 0);
640
641         if (!data) {
642             this.commError(SypStrings.ServerError);
643             $("#login_error").show();
644             window.setTimeout(function() {
645                     $("#password").focus().select();
646             }, 0);
647             return;
648         }
649         var xml = new OpenLayers.Format.XML().read(data);
650
651         switch (xml.documentElement.nodeName.toLowerCase()) {
652             case "error":
653                 switch (xml.documentElement.getAttribute("reason")) {
654                     case "server":
655                         this.commError(SypStrings.ServerError);
656                     break;
657                     case "unauthorized":
658                         this.commError(SypStrings.UnauthorizedError);
659                     break;
660                     case "request":
661                         this.commError(SypStrings.RequestError);
662                     break;
663                     default:
664                         this.commError(SypStrings.UnknownError);
665                     break;
666                 }
667                 $("#login_error").show();
668                 window.setTimeout(function() {
669                         $("#password").focus().select();
670                 }, 0);
671             break;
672             case "success":
673                 this.reset();
674                 $("#login_area").hide();
675             break;
676             default:
677                 this.commError(SypStrings.UnconsistentError);
678             break;
679         }
680     },
681
682     commError: function (message) {
683         $("#login_error").text(message);
684         if (message) {
685             $("#login_error").show();
686         } else {
687             $("#login_error").hide();
688         }
689     }
690 }
691
692 $(window).load(function () {
693     // if using .ready, ie triggers an error when trying to access
694     // document.namespaces
695     pwdMgr.init();
696     $("#newfeature_button").click(function () {
697         Admin.addNewFeature();
698     });
699     $("#editor_close").click(function () {
700         Admin.cancelCurrentFeature()
701     });
702     $("#feature_update").submit(function() {
703         try {
704             FeatureMgr.save(Admin.currentFeature);
705         } catch(e) {}
706         return false;
707     });
708     $("#feature_delete").submit(function() {
709         try {
710             FeatureMgr.del(Admin.currentFeature);
711         } catch(e) {}
712         return false;
713     });
714     $("#image_delete").click(function() {
715             alert (SypStrings.DisabledForDemo);
716             return;
717             $("#img").removeAttr('src');
718             // needs to rebuild element otherwise some browsers still
719             // display image.
720             $("#img").parent().html($("#img").parent().html());
721             $("#img").parent().hide();
722             $("#image_delete").hide();
723             $("#image_file").parent().show();
724     });
725
726     Admin.init();
727 });