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