1 /* Copyright (c) 2009 Arnaud Renevier, Inc, published under the modified BSD
4 // drag feature with tolerance
5 OpenLayers.Control.SypDragFeature = OpenLayers.Class (OpenLayers.Control.DragFeature, {
11 downFeature: function(pixel) {
12 OpenLayers.Control.DragFeature.prototype.downFeature.apply(this, arguments);
13 this.dragStart = (new Date()).getTime();
14 this.startPixel = pixel;
17 doneDragging: function(pixel) {
18 OpenLayers.Control.DragFeature.prototype.doneDragging.apply(this, arguments);
20 var passesTimeTolerance =
21 (new Date()).getTime() > this.dragStart + this.timeTolerance;
23 var xDiff = this.startPixel.x - pixel.x;
24 var yDiff = this.startPixel.y - pixel.y;
26 var passesPixelTolerance =
27 Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2)) > this.pixelTolerance;
29 if(passesTimeTolerance && passesPixelTolerance){
30 this.onComplete(this.feature, pixel);
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);
38 this.layer.drawFeature(this.feature, "select");
41 moveFeature: function(pixel) {
42 OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this, arguments);
43 this.layer.drawFeature(this.feature, "temporary");
46 overFeature: function (feature) {
47 // can only drag and drop currently selected feature
48 if (feature != Admin.currentFeature) {
51 OpenLayers.Control.DragFeature.prototype.overFeature.apply(this, arguments);
54 CLASS_NAME: "OpenLayers.Control.SypDragFeature"
59 ICON: "media/marker-normal.png",
60 SELECT_ICON: "media/marker-selected.png",
61 TEMPORARY_ICON: "media/marker-temp.png",
68 selFeatureControl: null,
69 moveFeatureControl: null,
70 addFeatureControl: null,
73 currentFeatureLocation: null,
76 this.map = new OpenLayers.Map ("map", {
78 new OpenLayers.Control.Navigation (),
79 new OpenLayers.Control.PanZoom ()
81 projection: new OpenLayers.Projection("EPSG:900913"),
82 displayProjection: new OpenLayers.Projection("EPSG:4326")
85 this.baseLayer = this.createBaseLayer ();
86 this.map.addLayer(this.baseLayer);
88 this.map.setCenter(new OpenLayers.LonLat(0, 0), 0);
89 if (sypSettings.loggedUser) {
90 this.dataLayer = this.createDataLayer (sypSettings.loggedUser);
91 this.map.addLayer(this.dataLayer);
97 this.addFeatureControl.deactivate();
98 this.moveFeatureControl.deactivate();
99 this.selFeatureControl.activate();
100 this.checkForFeatures();
101 $("#newfeature_button").show().val(SypStrings.AddItem);
102 $("#newfeature_button").unbind("click").click(function () {
103 Admin.addNewFeature();
107 createBaseLayer: function () {
108 return new OpenLayers.Layer.OSM("OSM");
111 createDataLayer: function (user) {
112 var styleMap = new OpenLayers.StyleMap (
114 externalGraphic: this.Markers.ICON,
115 graphicHeight: this.Markers.HEIGHT || 32
118 externalGraphic: this.Markers.TEMPORARY_ICON,
119 graphicHeight: this.Markers.HEIGHT || 32
122 externalGraphic: this.Markers.SELECT_ICON,
123 graphicHeight: this.Markers.HEIGHT || 32
126 var layer = new OpenLayers.Layer.GML("KML", "items.php?from_user=" + encodeURIComponent(user),
129 format: OpenLayers.Format.KML,
130 projection: this.map.displayProjection,
131 eventListeners: { scope: this,
132 loadend: this.dataLayerEndLoad
137 this.selFeatureControl = this.createSelectFeatureControl(layer)
138 this.map.addControl(this.selFeatureControl);
139 this.moveFeatureControl = this.createMoveFeatureControl(layer)
140 this.map.addControl(this.moveFeatureControl);
141 this.addFeatureControl = this.createNewfeatureControl();
142 this.map.addControl(this.addFeatureControl);
147 createMoveFeatureControl: function (layer) {
148 var control = new OpenLayers.Control.SypDragFeature(
154 createSelectFeatureControl: function (layer) {
155 var control = new OpenLayers.Control.SelectFeature(
157 onSelect: OpenLayers.Function.bind(this.onFeatureSelect, this)
162 createNewfeatureControl: function () {
163 var control = new OpenLayers.Control ();
164 var handler = new OpenLayers.Handler.Click(control, {
165 'click': OpenLayers.Function.bind(FeatureMgr.add, FeatureMgr)
167 control.handler = handler;
171 onFeatureSelect: function (feature) {
172 this.showEditor(feature);
174 this.selFeatureControl.deactivate();
175 this.moveFeatureControl.activate();
178 closeEditor: function() {
179 if (this.currentFeature && this.currentFeature.layer) {
180 this.selFeatureControl.unselect(this.currentFeature);
182 this.currentFeature = null;
183 this.currentFeatureLocation = null;
184 $("#img").removeAttr('src');
185 $("#img").parent().html($("#img").parent().html());
186 $("#img").parent().show();
187 $("#title, #description").val("");
189 // do it once before hidding and once after hidding to work in all cases
190 $("#title, #description").val("");
191 $("#image_file").parent().html($("#image_file").parent().html());
192 $(document).unbind("keydown");
193 this.checkForFeatures();
197 showEditor: function (feature) {
198 $("#newfeature_button").hide();
204 $(document).unbind("keydown").keydown(function(e) {
205 if (e.keyCode == 27) {
206 Admin.cancelCurrentFeature()
210 this.currentFeature = feature;
211 this.currentFeatureLocation = new OpenLayers.Pixel(feature.geometry.x, feature.geometry.y);
213 $("#instructions").text(SypStrings.DragDropHowto);
214 $("#title").val(feature.attributes.name);
215 var fullDesc = $(feature.attributes.description).parent();
216 $("#description").val(fullDesc.find('p').text());
217 var src = fullDesc.find('img').attr('src');
219 $("#img").parent().show();
220 $("#img").attr('src', src);
221 $("#image_file").parent().hide();
222 $("#image_delete").show();
224 $("#img").parent().hide();
225 $("#image_file").parent().show();
226 $("#image_delete").hide();
228 $("#title").select().focus();
231 dataLayerEndLoad: function() {
232 // only set zoom extent once
233 this.dataLayer.events.unregister('loadend', this, this.dataLayerEndLoad);
234 this.dataLayer.events.register('loadend', this, this.checkForFeatures);
236 if (!this.checkForFeatures()) {
241 var orig = this.Utils.mbr (this.dataLayer);
242 var centerBounds = new OpenLayers.Bounds();
244 var mapProj = map.getProjectionObject();
245 var sypOrigProj = new OpenLayers.Projection("EPSG:4326");
247 var bottomLeft = new OpenLayers.LonLat(orig[0],orig[1]);
248 bottomLeft = bottomLeft.transform(sypOrigProj, mapProj);
249 var topRight = new OpenLayers.LonLat(orig[2],orig[3])
250 topRight = topRight.transform(sypOrigProj, mapProj);
252 centerBounds.extend(bottomLeft);
253 centerBounds.extend(topRight);
254 map.zoomToExtent(centerBounds);
257 checkForFeatures: function () {
258 var features = this.dataLayer.features;
259 if (features.length != 0) {
260 $("#instructions").text(SypStrings.SelectHowto);
262 return !!features.length;
265 addNewFeature: function () {
267 $(document).unbind("keydown");
270 $(document).unbind("keydown").keydown(function(e) {
271 if (e.keyCode == 27) {
277 $("#newfeature_button").val("annuler");
278 $("#newfeature_button").unbind("click").click(cancel);
280 $("#instructions").text(SypStrings.AddHowto);
281 this.selFeatureControl.deactivate();
282 this.addFeatureControl.activate();
286 cancelCurrentFeature: function() {
287 if (AjaxMgr.running) {
290 var feature = this.currentFeature;
292 FeatureMgr.move (feature, this.currentFeatureLocation);
294 this.dataLayer.removeFeatures([feature]);
299 reloadLayer: function (layer) {
300 layer.destroyFeatures();
301 layer.loaded = false;
306 /* minimum bounds rectangle containing all feature locations.
307 * FIXME: if two features are close, but separated by 180th meridian,
308 * their mbr will span the whole earth. Actually, 179° lon and -170°
309 * lon are considerated very near.
311 mbr: function (layer) {
315 var mapProj = map.getProjectionObject();
316 var sypOrigProj = new OpenLayers.Projection("EPSG:4326");
318 for (var i =0; i < layer.features.length; i++) {
319 if (layer.features[i].cluster) {
320 features = features.concat(layer.features[i].cluster);
322 features = features.concat(layer.features);
331 if (features.length == 0) {
332 // keep default values
333 } else if (features.length == 1) {
334 // in case there's only one feature, we show an area of at least
336 var pos = features[0].geometry.getBounds().getCenterLonLat().clone();
337 var lonlat = pos.transform(mapProj, sypOrigProj);
339 minlon = Math.max (lonlat.lon - 2, -180);
340 maxlon = Math.min (lonlat.lon + 2, 180);
341 minlat = Math.max (lonlat.lat - 2, -90);
342 maxlat = Math.min (lonlat.lat + 2, 90);
344 for (var i = 0; i < features.length; i++) {
345 var pos = features[i].geometry.getBounds().getCenterLonLat().clone();
346 var lonlat = pos.transform(mapProj, sypOrigProj);
347 minlon = Math.min (lonlat.lon, minlon);
348 minlat = Math.min (lonlat.lat, minlat);
349 maxlon = Math.max (lonlat.lon, maxlon);
350 maxlat = Math.max (lonlat.lat, maxlat);
354 return [minlon, minlat, maxlon, maxlat];
359 escapeHTML: function (str) {
364 replace(/&/gm, '&').
365 replace(/'/gm, ''').
366 replace(/"/gm, '"').
367 replace(/>/gm, '>').
368 replace(/</gm, '<');
371 startsWith: function (str, prefix) {
372 return (str.slice(0, prefix.length) == prefix);
375 indexOf: function (array, item) {
376 if (array.indexOf !== undefined) {
377 return array.indexOf(item);
379 return OpenLayers.Util.indexOf(array, item);
392 var pos = map.getLonLatFromViewPortPx(evt.xy);
393 feature = this.update (null, pos, "", "", "");
394 Admin.addFeatureControl.deactivate();
395 Admin.selFeatureControl.select(feature);
398 move: function (feature, aLocation) {
399 if (!feature || !aLocation) {
402 var curLoc = feature.geometry;
403 feature.geometry.move(aLocation.x - curLoc.x, aLocation.y - curLoc.y);
404 feature.layer.drawFeature(feature);
407 update: function(feature, lonlat, imgurl, title, description) {
408 var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
410 feature = new OpenLayers.Feature.Vector(point);
411 Admin.dataLayer.addFeatures([feature]);
413 this.move (feature, point);
415 feature.attributes.name = title;
416 feature.attributes.description = "<p>" + Admin.Utils.escapeHTML(description) + "</p>"
417 + "<img src=\"" + imgurl + "\">"
421 del: function (feature) {
422 var form = $("#feature_delete");
423 form.find('input[name="fid"]').val(feature.fid);
426 oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
427 onsend: function() { $("#editor_throbber").css("visibility", "visible"); }
431 save: function (feature) {
432 var x = feature.geometry.x;
433 var y = feature.geometry.y;
435 var mapProj = feature.layer.map.getProjectionObject();
436 var lonlat = new OpenLayers.LonLat(x, y).
438 new OpenLayers.Projection("EPSG:4326"));
439 var form = $("#feature_update");
440 form.find('input[name="lon"]').val(lonlat.lon);
441 form.find('input[name="lat"]').val(lonlat.lat);
442 form.find('input[name="fid"]').val(feature.fid);
443 form.find('input[name="keep_img"]').val(
444 $("#img").attr("src") ? "yes": "no"
448 form.find('input[name="request"]').val("update");
450 form.find('input[name="request"]').val("add");
454 oncomplete: OpenLayers.Function.bind(this.ajaxReply, this),
455 onsend: function() { $("#editor_throbber").css("visibility", "visible"); }
459 ajaxReply: function (data) {
460 $("#editor_throbber").css("visibility", "hidden");
462 this.commError(SypStrings.ServerError);
466 var xml = new OpenLayers.Format.XML().read(data);
468 switch (xml.documentElement.nodeName.toLowerCase()) {
470 switch (xml.documentElement.getAttribute("reason")) {
472 $("#login_area").show();
473 $("#password").val("");
474 $("#user").val(sypSettings.loggedUser).focus().select();
475 $("#cookie_warning").show();
477 Admin.cancelCurrentFeature();
481 this.commError(SypStrings.ServerError);
485 this.commError(SypStrings.UnreferencedError);
486 Admin.reloadLayer(Admin.dataLayer);
490 this.commError(SypStrings.NochangeError);
494 this.commError(SypStrings.RequestError);
498 this.commError(SypStrings.ToobigError);
499 $("#image_file").parent().html($("#image_file").parent().html());
500 $("#image_file").focus();
503 this.commError(SypStrings.NotimageError);
504 $("#image_file").parent().html($("#image_file").parent().html());
505 $("#image_file").focus();
508 this.commError(SypStrings.UnknownError);
514 switch (xml.documentElement.getAttribute("request")) {
516 this.commSuccess(SypStrings.DelSucces);
517 var someFeature = false;
519 $.each($(xml).find("FEATURE,feature"), function () {
521 var id = parseFloat($(this).find("ID:first,id:first").text());
522 if ((id === null) || isNaN (id)) {
525 var features = Admin.dataLayer.features;
526 for (var idx = 0; idx < features.length; idx++) {
527 if (features[idx].fid == id) {
528 Admin.dataLayer.removeFeatures([features[idx]]);
532 if (someFeature == false) {
533 this.commError(SypStrings.UnconsistentError);
540 var someFeature = false;
542 $.each($(xml).find("FEATURE,feature"), function () {
544 var id = parseFloat($(this).find("ID:first,id:first").text());
545 if ((id === null) || isNaN (id)) {
549 var lon = parseFloat($(this).find("LON:first,lon:first").text());
550 if ((typeof (lon) != "number") || isNaN (lon) ||
551 (lon < -180) || (lon > 180)) {
555 var lat = parseFloat($(this).find("LAT:first,lat:first").text());
556 if ((typeof (lat) != "number") || isNaN (lat) ||
557 (lat < -90) || (lat > 90)) {
561 var mapProj = Admin.map.getProjectionObject();
562 var lonlat = new OpenLayers.LonLat (lon, lat).
563 transform( new OpenLayers.Projection("EPSG:4326"), mapProj);
565 var imgurl = $(this).find("IMGURL:first,imgurl:first").text();
566 var title = $(this).find("HEADING:first,heading:first").text();
567 var description = $(this).find("DESCRIPTION:first,description:first").text();
569 feature = self.update (Admin.currentFeature, lonlat, imgurl, title, description);
573 if (someFeature == false) {
574 this.commError(SypStrings.UnconsistentError);
576 this.commSuccess(SypStrings.UpdateSucces);
582 this.commError(SypStrings.UnconsistentError);
587 this.commError(SypStrings.UnconsistentError);
592 commSuccess: function (message) {
593 $("#server_comm").text(message);
594 $("#server_comm").removeClass().addClass("success");
597 commError: function (message) {
598 $("#server_comm").text(message);
599 $("#server_comm").removeClass().addClass("error");
603 /* maintains a queue of ajax queries, so I'm sure they all execute in the same
604 * order they were defined */
610 add: function(query) {
611 this._queue.push(query);
612 if (this._queue.length > 1) {
615 this._runQuery(query);
619 _runQuery: function(query) {
621 $('#api_frame').one("load", function() {
622 self.running = false;
624 if (typeof (query.oncomplete) == "function") {
627 if (this.contentDocument) {
628 body = this.contentDocument.body;
629 } else if (this.contentWindow) {
630 body = this.contentWindow.document.body;
632 body = document.frames[this.id].document.body;
636 query.oncomplete(body.innerHTML);
638 query.oncomplete(null);
642 query.form.attr("action", "api.php");
643 query.form.attr("target", "api_frame");
644 query.form.attr("method", "post");
646 query.form.get(0).submit();
647 if (typeof (query.onsend) == "function") {
652 _reqEnd: function() {
654 if (this._queue.length > 0) {
655 this._reqEnd(this._queue[0]);
663 $("#login_form").submit(this.submit);
664 $("#user").focus().select();
671 submit: function () {
673 pwdMgr.commError("");
675 form: $("#login_form"),
677 $("#pwd_throbber").css("visibility", "visible");
678 $("#login_error").hide();
680 // we need a timeout; otherwise those fields will not be submitted
681 window.setTimeout(function() {
682 // removes focus from #password before disabling it. Otherwise, opera
683 // prevents re-focusing it after re-enabling it.
684 $("#user, #password").blur();
685 $("#login_submit, #user, #password").attr("disabled", "disabled");
688 oncomplete: OpenLayers.Function.bind(pwdMgr.ajaxReply, pwdMgr)
695 ajaxReply: function (data) {
697 $("#pwd_throbber").css("visibility", "hidden");
698 // here, we need a timeout because onsend timeout sometimes has not been triggered yet
699 window.setTimeout(function() {
700 $("#login_submit, #user, #password").removeAttr("disabled");
704 this.commError(SypStrings.ServerError);
705 $("#login_error").show();
706 window.setTimeout(function() {
707 $("#user").focus().select();
711 var xml = new OpenLayers.Format.XML().read(data);
713 switch (xml.documentElement.nodeName.toLowerCase()) {
715 switch (xml.documentElement.getAttribute("reason")) {
717 this.commError(SypStrings.ServerError);
720 this.commError(SypStrings.UnauthorizedError);
723 this.commError(SypStrings.RequestError);
726 this.commError(SypStrings.UnknownError);
729 $("#login_error").show();
730 window.setTimeout(function() {
731 $("#user").focus().select();
735 $("#login_area").hide();
737 user = $(xml).find("USER,user").text();
738 sypSettings.loggedUser = user;
740 if (Admin.selFeatureControl) {
741 Admin.selFeatureControl.destroy();
743 if (Admin.moveFeatureControl) {
744 Admin.moveFeatureControl.destroy();
746 if (Admin.addFeatureControl) {
747 Admin.addFeatureControl.destroy();
749 if (Admin.dataLayer) {
750 Admin.dataLayer.destroy();
753 Admin.dataLayer = Admin.createDataLayer(user);
754 Admin.map.addLayer(Admin.dataLayer);
759 this.commError(SypStrings.UnconsistentError);
764 commError: function (message) {
765 $("#login_error").text(message);
767 $("#login_error").show();
769 $("#login_error").hide();
774 $(window).load(function () {
775 // if using .ready, ie triggers an error when trying to access
776 // document.namespaces
778 $("#newfeature_button").click(function () {
779 Admin.addNewFeature();
781 $("#editor_close").click(function () {
782 Admin.cancelCurrentFeature()
784 $("#feature_update").submit(function() {
786 FeatureMgr.save(Admin.currentFeature);
790 $("#feature_delete").submit(function() {
792 FeatureMgr.del(Admin.currentFeature);
796 $("#image_delete").click(function() {
797 $("#img").removeAttr('src');
798 // needs to rebuild element otherwise some browsers still
800 $("#img").parent().html($("#img").parent().html());
801 $("#img").parent().hide();
802 $("#image_delete").hide();
803 $("#image_file").parent().show();