]> dev.renevier.net Git - syj.git/blob - public/js/syj.js
allow setting initial position with get params
[syj.git] / public / js / syj.js
1 /*  This file is part of Syj, Copyright (c) 2010 Arnaud Renevier,
2     and is published under the AGPL license. */
3
4 // avoid openlayers alerts
5 OpenLayers.Console.userError = function(error) {
6     SYJView.messenger.setMessage(error, "error");
7 };
8
9 var SyjSaveUI = {
10     status: "unknown",
11
12     init: function() {
13         $("geom_title").observe('contentchange', this.enableSubmit.bindAsEventListener(this));
14         return this;
15     },
16
17     enable: function() {
18         if (this.status === "enabled") {
19             return this;
20         }
21         this.enableSubmit();
22         $("geom_title").disabled = false;
23         $("geom_title").activate();
24         $("geomform").removeClassName("disabled");
25         this.status = "enabled";
26         return this;
27     },
28
29     disable: function() {
30         if (this.status === "disabled") {
31             return this;
32         }
33         this.disableSubmit();
34         $("geom_title").blur();
35         $("geom_title").disabled = true;
36         $("geomform").addClassName("disabled");
37         this.status = "disabled";
38         return this;
39     },
40
41     enableSubmit: function() {
42         $("geom_submit").disabled = false;
43         $("geom_accept").disabled = false;
44         this.status = "partial";
45         return this;
46     },
47
48     disableSubmit: function() {
49         $("geom_submit").blur();
50         $("geom_submit").disabled = true;
51         $("geom_accept").blur();
52         $("geom_accept").disabled = true;
53         this.status = "partial";
54         return this;
55     }
56 };
57
58 var SYJPathLength = (function(){
59     return {
60         update: function() {
61             var pathLength = 0, unit;
62             if (SYJView.mode === 'view') {
63                 if (SYJView.viewLayer.features.length) {
64                     pathLength = SYJView.viewLayer.features[0].geometry.getGeodesicLength(Mercator);
65                 }
66             } else {
67                 pathLength = SYJView.editControl.handler.line.geometry.getGeodesicLength(Mercator);
68             }
69
70             if (pathLength === 0) {
71                 $("path-length").hide();
72                 return;
73             }
74             $("path-length").show();
75
76             if (pathLength < 1000) {
77                 // precision: 1 cm
78                 pathLength = Math.round(pathLength * 100) / 100;
79                 unit = 'm';
80             } else {
81                 // precision: 1 m
82                 pathLength = Math.round(pathLength) / 1000;
83                 unit = 'km';
84             }
85             $("path-length-content").update(pathLength + ' ' + unit);
86         }
87     };
88 }());
89
90 var SYJDataUi = (function() {
91     var deck = null,
92         infotoggler = null,
93         getdeck = function() {
94             if (!deck) {
95                 deck = new Deck("data_controls");
96             }
97             return deck;
98         },
99         getinfotoggler = function() {
100             if (!infotoggler) {
101                 infotoggler = new Toggler('path-infos-content');
102                 $("path-infos-toggler").insert({bottom: infotoggler.element});
103                 $("path-infos-anchor").observe('click', function(evt) {
104                     evt.stop();
105                     infotoggler.toggle(evt);
106                 });
107                 document.observe('toggler:open', function(evt) {
108                     if (evt.memo === infotoggler) {
109                         // XXX: update informations
110                     }
111                 });
112             }
113             return infotoggler;
114         };
115     return {
116         viewmode: function() {
117             getdeck().setIndex(0);
118             if ($("path-infos")) {
119                 getinfotoggler();
120                 getinfotoggler().close();
121                 $("path-infos").show();
122             }
123         },
124         editmode: function() {
125             getdeck().setIndex(1);
126             if ($("path-infos")) {
127                 $("path-infos").hide();
128             }
129         }
130     };
131 }());
132
133 OpenLayers.Handler.SyjModifiablePath = OpenLayers.Class(OpenLayers.Handler.ModifiablePath, {
134     mouseup: function(evt) {
135         // do not add a point when navigating
136         var mapControls = this.control.map.controls, idx, ctrl;
137
138         for (idx = mapControls.length; idx-->0; ) {
139             ctrl = mapControls[idx];
140             if (this.isCtrlNavigationActive(ctrl, evt)) {
141                 return true;
142             }
143         }
144         return OpenLayers.Handler.ModifiablePath.prototype.mouseup.apply(this, arguments);
145     },
146
147     addPoints: function(pixel) {
148         // redraw before last point. As we have a special style for last point, we
149         // need to redraw before last point after adding a new point (otherwise, it
150         // keeps special style forever)
151         var oldpoint = this.point;
152         OpenLayers.Handler.ModifiablePath.prototype.addPoints.apply(this, arguments);
153         this.layer.drawFeature(oldpoint);
154     },
155
156     isCtrlNavigationActive: function(ctrl, evt) {
157         var tolerance = 4, xDiff, yDiff;
158
159         if (!(ctrl instanceof OpenLayers.Control.Navigation)) {
160             return false;
161         }
162
163         if (ctrl.zoomBox &&
164             ctrl.zoomBox.active &&
165             ctrl.zoomBox.handler &&
166             ctrl.zoomBox.handler.active &&
167             ctrl.zoomBox.handler.dragHandler &&
168             ctrl.zoomBox.handler.dragHandler.start) {
169             return true;
170         }
171
172         if (!ctrl.dragPan ||
173             !ctrl.dragPan.active ||
174             !ctrl.dragPan.handler ||
175             !ctrl.dragPan.handler.started ||
176             !ctrl.dragPan.handler.start) {
177             return false;
178         }
179
180         // if mouse moved 4 or less pixels, consider it has not moved.
181         tolerance = 4;
182
183         xDiff = evt.xy.x - ctrl.dragPan.handler.start.x;
184         yDiff = evt.xy.y - ctrl.dragPan.handler.start.y;
185
186         if (Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2)) <= tolerance) {
187             return false;
188         }
189         return true;
190     },
191
192     finalize: function(cancel) {
193         // do nothing. We don't want to finalize path
194     }
195 });
196
197 var styleMap = {
198     edit: new OpenLayers.StyleMap({
199         "default": new OpenLayers.Style({
200             pointRadius: "${radius}", // sized according to type attribute
201             fillColor: "#ffcc66",
202             strokeColor: "#ff9933",
203             strokeWidth: 2,
204             strokeOpacity: "${opacity}",
205             fillOpacity: "${opacity}"
206         },
207         {
208             context: {
209                 radius: function(feature) {
210                     var features;
211
212                     if (!(feature.geometry instanceof OpenLayers.Geometry.Point)) {
213                         return 0;
214                     }
215                     if (feature.type === "middle") {
216                         return 4;
217                     }
218                     features = feature.layer.features;
219                     if (OpenLayers.Util.indexOf(features, feature) === 0) {
220                         return 5;
221                     } else if (OpenLayers.Util.indexOf(features, feature) === features.length - 1) {
222                         return 5;
223                     }
224                     return 3;
225                 },
226                 opacity: function (feature) {
227                     if (feature.type === "middle") {
228                         return 0.5;
229                     } else {
230                         return 1;
231                     }
232                 }
233             }
234         }),
235
236         "select": new OpenLayers.Style({
237             externalGraphic: "icons/delete.png",
238             graphicHeight: 16
239         }),
240
241         "select_for_canvas": new OpenLayers.Style({
242             strokeColor: "blue",
243             fillColor: "lightblue"
244         })
245
246     }),
247
248     view: new OpenLayers.StyleMap({
249         "default": new OpenLayers.Style({
250             strokeColor: "blue",
251             strokeWidth: 5,
252             strokeOpacity: 0.7
253         })
254     })
255 };
256
257 var WGS84 = new OpenLayers.Projection("EPSG:4326");
258 var Mercator = new OpenLayers.Projection("EPSG:900913");
259
260 var SYJView = {
261     viewLayer: null,
262     editControl: null,
263     map: null,
264     wkt: new OpenLayers.Format.WKT({ internalProjection: Mercator, externalProjection: WGS84 }),
265     needsFormResubmit: false,
266     unsavedRoute: null,
267     mode: 'view',
268
269     init: function() {
270         var externalGraphic, baseURL, baseLayer, layerOptions, hidemessenger;
271
272         // is svg context, opera does not resolve links with base element is svg context
273         externalGraphic = styleMap.edit.styles.select.defaultStyle.externalGraphic;
274         baseURL = document.getElementsByTagName("base")[0].href;
275         styleMap.edit.styles.select.defaultStyle.externalGraphic = baseURL + externalGraphic;
276
277         this.map = new OpenLayers.Map('map', {
278             controls: [
279                 new OpenLayers.Control.Navigation(),
280                 new OpenLayers.Control.PanZoom(),
281                 new OpenLayers.Control.Attribution()
282             ],
283             theme: null
284         });
285
286         baseLayer = new OpenLayers.Layer.OSM("OSM", null, { wrapDateLine: true , attribution: SyjStrings.osmAttribution });
287
288         layerOptions = {format:     OpenLayers.Format.WKT,
289                         projection: WGS84,
290                         styleMap:   styleMap.view,
291                         attribution: SyjStrings.geomAttribution };
292
293         this.viewLayer = new OpenLayers.Layer.Vector("View Layer", layerOptions);
294         this.map.addLayers([baseLayer, this.viewLayer]);
295
296         if ($("edit-btn")) {
297             $("edit-btn").observe('click', function() {
298                 $("geom_submit").value = SyjStrings.editAction;
299                 this.messenger.hide();
300                 this.editMode();
301                 this.mode = 'edit';
302             }.bind(this));
303         }
304
305         if ($("create-btn")) {
306             $("create-btn").observe('click', function() {
307                 $("geom_submit").value = SyjStrings.createAction;
308                 this.messenger.hide();
309                 this.editMode();
310                 this.mode = 'create';
311             }.bind(this));
312         }
313
314         if ($("clone-btn")) {
315             $("clone-btn").observe('click', function() {
316                 $("geom_submit").value = SyjStrings.cloneAction;
317                 $("geom_title").value = "";
318                 this.messenger.hide();
319                 this.editMode();
320                 this.mode = 'create';
321             }.bind(this));
322         }
323
324         $("geomform").ajaxize({
325                 presubmit: this.prepareForm.bind(this),
326                 onSuccess: this.saveSuccess.bind(this),
327                 onFailure: this.saveFailure.bind(this)
328                 });
329         SyjSaveUI.init();
330
331         this.messenger = $('message');
332         hidemessenger = this.messenger.empty();
333         new CloseBtn(this.messenger, {
334             style: {
335                 margin: "-1em"
336             }
337         });
338         if (hidemessenger) {
339             this.messenger.hide();
340         }
341
342         if (typeof gInitialGeom !== "undefined" && typeof gInitialGeom.data !== "undefined") {
343             this.viewLayer.addFeatures([this.wkt.read(gInitialGeom.data)]);
344             // XXX: ie has not guessed height of map main div yet during map
345             // initialisation. Now, it will read it correctly.
346             this.map.updateSize();
347             this.map.zoomToExtent(this.viewLayer.getDataExtent());
348         } else {
349             this.initMaPos(gInitialPos);
350         }
351
352         document.observe('simplebox:shown', this.observer.bindAsEventListener(this));
353         SYJPathLength.update();
354
355         if (window.FileList && window.FileReader) {
356             $("map").observe("dragenter", function(evt) { evt.stop();});
357             $("map").observe("dragover", function(evt) { evt.stop();});
358             $("map").observe("drop", function(evt) {
359                 evt.stop();
360                 if (this.mode !== "view" || this.viewLayer.features.length) {
361                     return;
362                 }
363                 if (!evt.dataTransfer.files.length) {
364                     return;
365                 }
366                 var file = evt.dataTransfer.files[0];
367                 var reader = new FileReader();
368                 var readerror = function() {
369                     this.messenger.setMessage(SyjStrings.dragFileError, "warn");
370                 }.bind(this);
371                 reader.onload = function(evt) {
372                     if (evt.error) {
373                         readerror();
374                         return;
375                     }
376
377                     var results = null;
378                     var content = evt.target.result;
379
380                     var engine, i;
381                     var formats = ['KML', 'GPX'];
382
383                     for (i = 0; i < formats.length; i++) {
384                         engine = new OpenLayers.Format[formats[i]]({ internalProjection: Mercator, externalProjection: WGS84 });
385                         try {
386                             results = engine.read(content);
387                         } catch(e) {
388                         }
389                         if (results || results.length) {
390                             continue;
391                         }
392                     }
393                     if (!results || !results.length) {
394                         readerror();
395                         return;
396                     }
397
398
399                     var vector = results[0];
400                     if (vector.geometry.CLASS_NAME !== "OpenLayers.Geometry.LineString") {
401                         readerror();
402                         return;
403                     }
404                     this.viewLayer.addFeatures([vector]);
405                     this.map.zoomToExtent(this.viewLayer.getDataExtent());
406
407                     if ($("edit-btn")) {
408                         $("edit-btn").click();
409                     } else if ($("create-btn")) {
410                         $("create-btn").click();
411                     }
412
413                     if (this.editControl.handler.realPoints.length < 2) {
414                         SyjSaveUI.disable();
415                     } else {
416                         SyjSaveUI.enable();
417                     }
418
419
420                     if (vector.data && vector.data.name) {
421                         $("geom_title").value = vector.data.name;
422                     }
423                  }.bind(this);
424                 reader.readAsText(file);
425            }.bind(this));
426         }
427     },
428
429     initMaPos: function (aPos) {
430         var extent = null, center = null, zoom = 0;
431
432         if (aPos.hasOwnProperty('lon') && aPos.hasOwnProperty('lat') && aPos.hasOwnProperty('zoom')) {
433             center = new OpenLayers.LonLat(parseFloat(aPos.lon), parseFloat(aPos.lat)).transform(WGS84, Mercator);
434             zoom = parseInt(aPos.zoom);
435         } else if (aPos.hasOwnProperty('minlon') && aPos.hasOwnProperty('minlat')
436                     && aPos.hasOwnProperty('maxlon') && aPos.hasOwnProperty('maxlat')) {
437             extent = new OpenLayers.Bounds(aPos.minlon, aPos.minlat, aPos.maxlon, aPos.maxlat)
438                                          .transform(WGS84, Mercator);
439         } else {
440             extent = new OpenLayers.Bounds(-160, -70, 160, 70).transform(WGS84, Mercator);
441         }
442
443         if (extent) {
444             this.map.zoomToExtent(extent);
445         } else {
446             this.map.setCenter(center, zoom);
447         }
448     },
449
450     observer: function(evt) {
451         if (evt.eventName === "simplebox:shown" && evt.memo.element !== $("termsofusearea")) {
452             this.messenger.hide();
453         }
454     },
455
456     prepareForm: function(form) {
457         if (!LoginMgr.logged && !$("geom_accept").checked) {
458             this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
459             $("geom_accept_container").highlight('#F08080');
460             $("geom_accept").activate();
461             return false;
462         }
463
464         var line, realPoints, idx;
465
466         line = new OpenLayers.Geometry.LineString();
467         realPoints = this.editControl.handler.realPoints;
468         for (idx = 0; idx < realPoints.length; idx++) {
469             line.addComponent(realPoints[idx].geometry.clone());
470         }
471         this.viewLayer.addFeatures(new OpenLayers.Feature.Vector(line));
472
473         this.viewMode();
474
475         $("geom_data").value = this.wkt.write(new OpenLayers.Feature.Vector(line));
476         if (this.mode === "edit" && typeof gLoggedInfo.pathid !== "undefined") {
477             $("geomform").setAttribute("action", "path/" + gLoggedInfo.pathid.toString() + '/update');
478         } else {
479             $("geomform").setAttribute("action", "path");
480         }
481         this.needsFormResubmit = false;
482         SyjSaveUI.disable.bind(SyjSaveUI).defer();
483         this.messenger.hide();
484         return true;
485     },
486
487     viewMode: function() {
488         var handler = this.editControl.handler;
489         OpenLayers.Handler.ModifiablePath.prototype.finalize.apply(handler, arguments);
490         // we need to recreate them on next createFeature; otherwise
491         // they'll reference destroyed features
492         delete(handler.handlers.drag);
493         delete(handler.handlers.feature);
494         this.editControl.deactivate();
495     },
496
497     editMode: function() {
498         var components, point0, point, pixels, pixel, idx;
499
500         this.initEditControl();
501
502         this.editControl.activate();
503         if (this.viewLayer.features.length) {
504             components = this.viewLayer.features[0].geometry.components;
505             point0 = components[0];
506             if (point0) {
507                 pixel = this.map.getPixelFromLonLat(new OpenLayers.LonLat(point0.x, point0.y));
508                 this.editControl.handler.createFeature(pixel);
509                 this.editControl.handler.lastUp = pixel;
510                 pixels = [];
511                 for (idx = 1; idx < components.length; idx++) {
512                     point = components[idx];
513                     pixels.push(this.map.getPixelFromLonLat(new OpenLayers.LonLat(point.x, point.y)));
514                 }
515                 this.editControl.handler.addPoints(pixels);
516             }
517             this.unsavedRoute = {
518                 features: this.viewLayer.features.invoke('clone'),
519                 title: $("geom_title").value
520             };
521         }
522
523         this.viewLayer.destroyFeatures();
524
525         SYJDataUi.editmode();
526         if (this.editControl.handler.realPoints && this.editControl.handler.realPoints.length >= 2) {
527             SyjSaveUI.disableSubmit();
528         } else {
529             SyjSaveUI.disable();
530         }
531     },
532
533     initEditControl: function() {
534         var styles;
535
536         if (this.editControl) {
537             return;
538         }
539
540         this.editControl = new OpenLayers.Control.DrawFeature(new OpenLayers.Layer.Vector(), OpenLayers.Handler.SyjModifiablePath, {
541             callbacks: {
542                 modify: function(f, line) {
543                     SYJPathLength.update();
544                     if (!SYJView.unsavedRoute) {
545                         SYJView.unsavedRoute = {};
546                     }
547                     if (this.handler.realPoints.length < 2) {
548                         SyjSaveUI.disable();
549                     } else {
550                         SyjSaveUI.enable();
551                     }
552                 }
553             },
554
555             handlerOptions: {
556                 layerOptions: {
557                     styleMap: styleMap.edit
558                 }
559             }
560         });
561         this.map.addControl(this.editControl);
562         if (this.editControl.layer.renderer instanceof OpenLayers.Renderer.Canvas) {
563             // using externalGraphic with canvas renderer is definitively too buggy
564             styles = this.editControl.handler.layerOptions.styleMap.styles;
565             styles.select = styles.select_for_canvas;
566         }
567     },
568
569     saveSuccess: function(transport) {
570       this.unsavedRoute = null;
571
572       if (transport.responseJSON && (typeof transport.responseJSON.redirect === "string")) {
573           location = transport.responseJSON.redirect;
574           return;
575       }
576
577       this.messenger.setMessage(SyjStrings.saveSuccess, "success");
578       SYJDataUi.viewmode();
579       document.title = $('geom_title').value;
580     },
581
582     saveFailure: function(transport) {
583         var httpCode = 0, message = "";
584
585         if (transport) {
586             httpCode = transport.getStatus();
587         }
588         switch (httpCode) {
589             case 0:
590                 message = SyjStrings.notReachedError;
591             break;
592             case 400:
593             case 404:
594                 message = SyjStrings.requestError;
595                 if (transport.responseJSON) {
596                     switch (transport.responseJSON.message) {
597                         case "uniquepath":
598                             message = SyjStrings.uniquePathError;
599                         break;
600                         default:
601                         break;
602                     }
603                 }
604             break;
605             case 403:
606                 message = "";
607                 SYJLogin.messenger.setMessage(SyjStrings.loginNeeded, "warn");
608                 SYJLogin.modalbox.show();
609                 this.needsFormResubmit = true;
610             break;
611             case 410:
612                 message = SyjStrings.gonePathError;
613             break;
614             case 500:
615                 message = SyjStrings.serverError;
616                 this.needsFormResubmit = true;
617             break;
618             default:
619                 message = SyjStrings.unknownError;
620             break;
621         }
622
623         this.editMode();
624         // is some cases, we let the user resubmit, in some other cases, he
625         // needs to modify the path before submitting again
626         if (this.needsFormResubmit) {
627             SyjSaveUI.enable();
628         }
629
630         this.messenger.setMessage(message, "error");
631     }
632 };
633
634 var SYJModalClass = Class.create({
635     type: "",
636
637     init: function() {
638         this.area = $(this.type + '_area');
639         this.messenger = $(this.type + "_message");
640         this.modalbox = new SimpleBox(this.area, {
641             closeMethods: ["onescapekey", "onouterclick", "onbutton"]
642         });
643
644         $(this.type + "_control_anchor").observe("click", function(evt) {
645             this.modalbox.show();
646             evt.stop();
647         }.bindAsEventListener(this));
648
649         document.observe('simplebox:shown', this.observer.bindAsEventListener(this));
650         document.observe('simplebox:hidden', this.observer.bindAsEventListener(this));
651
652         $(this.type + "form").ajaxize({
653             presubmit: this.presubmit.bind(this),
654             onSuccess: this.success.bind(this),
655             onFailure: this.failure.bind(this)
656         });
657     },
658
659     checkNotEmpty: function(input, message) {
660         if ($(input).value.strip().empty()) {
661             this.messenger.setMessage(message, "warn");
662             $(input).highlight('#F08080').activate();
663             return false;
664         }
665         return true;
666     },
667
668     observer: function(evt) {
669         var simplebox, input;
670
671         if (evt.eventName === "simplebox:shown" && evt.memo.element !== $("termsofusearea")) {
672             simplebox = evt.memo;
673             if (simplebox === this.modalbox) {
674                 input = this.area.select('input[type="text"]')[0];
675                 (function () {
676                     input.activate();
677                 }).defer();
678             } else {
679                 this.modalbox.hide();
680             }
681
682         } else if (evt.eventName === "simplebox:hidden" && evt.memo.element !== $("termsofusearea")) {
683             simplebox = evt.memo;
684             if (simplebox === this.modalbox) {
685                 this.reset();
686             }
687         }
688     },
689
690     failure: function(transport) {
691         var httpCode = 0, message = SyjStrings.unknownError, input; // default message error
692
693         if (transport) {
694             httpCode = transport.getStatus();
695         }
696
697         switch (httpCode) {
698             case 0:
699                 message = SyjStrings.notReachedError;
700             break;
701             case 400:
702             case 404:
703             case 410:
704                 message = SyjStrings.requestError;
705             break;
706             case 500:
707                 message = SyjStrings.serverError;
708             break;
709         }
710
711         this.messenger.setMessage(message, "error");
712         input = this.area.select('input[type="text"]')[0];
713         input.highlight('#F08080').activate();
714     },
715
716     reset: function() {
717         this.messenger.hide();
718         this.area.select('.message').invoke('setMessageStatus', null);
719     }
720 });
721
722 var SYJUserClass = Class.create(SYJModalClass, {
723     type: "user",
724     toubox: null,
725
726     init: function($super) {
727         $super();
728         $("termsofusearea").hide();
729
730         $$("#user_termsofuse_anchor, #geom_termsofuse_anchor").invoke('observe', "click", function(evt) {
731             if (!this.toubox) {
732                 this.toubox = new SimpleBox($("termsofusearea"), {
733                     closeMethods: ["onescapekey", "onouterclick", "onbutton"]
734                 });
735             }
736             this.toubox.show();
737             if (!$("termsofuseiframe").getAttribute("src")) {
738                 $("termsofusearea").show();
739                 $("termsofuseiframe").setAttribute("src", evt.target.href);
740             }
741             evt.stop();
742         }.bindAsEventListener(this));
743
744         $$("#login_area_create > a").invoke('observe', 'click',
745             function(evt) {
746                 this.modalbox.show();
747                 evt.stop();
748             }.bindAsEventListener(this));
749
750         $("user_pseudo-desc").hide();
751         $("user_pseudo").observe('contentchange', function(evt) {
752             var value = evt.target.value;
753             PseudoChecker.reset();
754             if (value && !(value.match(/^[a-zA-Z0-9_.]+$/))) {
755                 $("user_pseudo-desc").show().setMessageStatus("warn");
756             } else {
757                 $("user_pseudo-desc").hide();
758             }
759         }).timedobserve(function() {
760             PseudoChecker.check();
761         });
762
763         $("user_password").observe('contentchange', function(evt) {
764             if (evt.target.value.length < 6) {
765                 $("user_password-desc").setMessageStatus("warn");
766             } else {
767                 $("user_password-desc").setMessageStatus("success");
768             }
769         }.bindAsEventListener(this));
770
771         $('account-create-anchor').insert({after: new Toggler('account-info').element});
772     },
773
774     presubmit: function() {
775         this.messenger.hide();
776         PseudoChecker.reset();
777         if (!(this.checkNotEmpty("user_pseudo", SyjStrings.userEmptyWarn))) {
778             return false;
779         }
780
781         if (!($("user_pseudo").value.match(/^[a-zA-Z0-9_.]+$/))) {
782             $("user_pseudo-desc").show().setMessageStatus("warn");
783             $("user_pseudo").highlight('#F08080').activate();
784             return false;
785         }
786
787         if (PseudoChecker.exists[$("user_pseudo").value]) {
788             PseudoChecker.availableMessage(false);
789             $("user_pseudo").highlight('#F08080').activate();
790             return false;
791         }
792
793         if (!(this.checkNotEmpty("user_password", SyjStrings.passwordEmptyWarn))) {
794             return false;
795         }
796
797         if ($("user_password").value.length < 6) {
798             $("user_password-desc").setMessageStatus("warn");
799             $("user_password").highlight('#F08080').activate();
800             return false;
801         }
802
803         if ($("user_password").value !== $("user_password_confirm").value) {
804             this.messenger.setMessage(SyjStrings.passwordNoMatchWarn, "warn");
805             $("user_password").highlight('#F08080').activate();
806             return false;
807         }
808
809         if (!(this.checkNotEmpty("user_email", SyjStrings.emailEmptyWarn))) {
810             return false;
811         }
812
813         if (!$("user_accept").checked) {
814             this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
815             $("user_accept_container").highlight('#F08080');
816             $("user_accept").activate();
817             return false;
818         }
819
820         this.reset();
821         return true;
822     },
823
824     success: function(transport) {
825         if (!transport.responseJSON ||
826             typeof transport.responseJSON.pseudo !== "string"
827             ) {
828             this.messenger.setMessage(SyjStrings.unknownError, "error");
829             return;
830         }
831
832         LoginMgr.login(transport.responseJSON.pseudo);
833         SYJView.messenger.setMessage(SyjStrings.userSuccess, "success");
834         this.modalbox.hide();
835         if (SYJView.needsFormResubmit) {
836             SYJView.messenger.addMessage(SyjStrings.canResubmit);
837             $("geom_submit").activate();
838         }
839     },
840
841     failure: function($super, transport) {
842         var httpCode = 0, focusInput = null, message = "";
843
844         if (transport) {
845             httpCode = transport.getStatus();
846         }
847
848         focusInput = null;
849         message = "";
850
851         switch (httpCode) {
852             case 400:
853                 if (transport.responseJSON) {
854                     switch (transport.responseJSON.message) {
855                         case "invalidemail":
856                             message = SyjStrings.emailInvalidWarn;
857                             focusInput = $("user_email");
858                         break;
859                         case "uniquepseudo":
860                             PseudoChecker.availableMessage(false);
861                             focusInput = $("user_pseudo");
862                         break;
863                         case "uniqueemail":
864                             message = SyjStrings.uniqueEmailError;
865                             focusInput = $("user_email");
866                         break;
867                     }
868                 }
869             break;
870         }
871
872         if (focusInput) {
873             if (message) {
874                 this.messenger.setMessage(message, "error");
875             }
876             focusInput.highlight('#F08080').activate();
877             return;
878         }
879
880         $super(transport);
881     }
882 });
883 var SYJUser = new SYJUserClass();
884
885 var SYJLoginClass = Class.create(SYJModalClass, {
886     type: "login",
887
888     init: function($super) {
889         $super();
890     },
891
892     presubmit: function() {
893         this.messenger.hide();
894         if (!(this.checkNotEmpty("login_user", SyjStrings.userEmptyWarn))) {
895             return false;
896         }
897
898         this.reset();
899         return true;
900     },
901
902     success: function(transport) {
903         if (!transport.responseJSON ||
904             typeof transport.responseJSON.iscreator !== "boolean" ||
905             typeof transport.responseJSON.pseudo !== "string"
906             ) {
907             this.messenger.setMessage(SyjStrings.unknownError, "error");
908             return;
909         }
910         LoginMgr.login(transport.responseJSON.pseudo, transport.responseJSON.iscreator);
911
912         SYJView.messenger.setMessage(SyjStrings.loginSuccess, "success");
913         this.modalbox.hide();
914         if (SYJView.needsFormResubmit) {
915             SYJView.messenger.addMessage(SyjStrings.canResubmit);
916             $("geom_submit").activate();
917         }
918     },
919
920     failure: function($super, transport) {
921         var httpCode = 0, focusInput = null, message = "";
922
923         if (transport) {
924             httpCode = transport.getStatus();
925         }
926
927         focusInput = null;
928         message = "";
929
930         switch (httpCode) {
931             case 403:
932                 message = SyjStrings.loginFailure;
933                 focusInput = $("login_user");
934             break;
935         }
936
937         if (message) {
938             this.messenger.setMessage(message, "error");
939             if (focusInput) {
940                 focusInput.highlight('#F08080').activate();
941             }
942             return;
943         }
944
945         $super(transport);
946     }
947 });
948 var SYJLogin = new SYJLoginClass();
949
950 var SYJNewpwdClass = Class.create(SYJModalClass, {
951     type: "newpwd",
952
953     presubmit: function() {
954         if (!(this.checkNotEmpty("newpwd_email", SyjStrings.emailEmptyWarn))) {
955             return false;
956         }
957         this.reset();
958         return true;
959     },
960     success: function(transport) {
961         SYJView.messenger.setMessage(SyjStrings.newpwdSuccess, "success");
962         this.modalbox.hide();
963     }
964
965 });
966 var SYJNewpwd = new SYJNewpwdClass();
967
968 var LoginMgr = Object.extend(gLoggedInfo, {
969     controlsdeck: null,
970
971     updateUI: function() {
972         if (!this.controlsdeck) {
973             this.controlsdeck = new Deck("login_controls");
974         }
975         if (this.logged) {
976             this.controlsdeck.setIndex(1);
977             $$(".logged-hide").invoke('hide');
978             $$(".logged-show").invoke('show');
979         } else {
980             this.controlsdeck.setIndex(0);
981             $$(".logged-hide").invoke('show');
982             $$(".logged-show").invoke('hide');
983         }
984
985         if ($("edit-btn")) {
986             if (this.iscreator && SYJView.mode === 'view') {
987                 $("edit-btn").show();
988             } else {
989                 $("edit-btn").hide();
990             }
991         }
992     },
993
994     login: function(aPseudo, aIsCreator) {
995         if (typeof aIsCreator === "boolean") {
996             this.iscreator = aIsCreator;
997         }
998         this.logged = true;
999         $$('.logged-pseudo').each(function(elt) {
1000             $A(elt.childNodes).filter(function(node) {
1001                 return (node.nodeType === 3 || node.tagName.toLowerCase() === 'br');
1002             }).each(function(node) {
1003                 node.nodeValue = node.nodeValue.replace('%s', aPseudo);
1004             });
1005         });
1006         this.updateUI();
1007     }
1008 });
1009
1010 var PseudoChecker = {
1011     req: null,
1012     exists: {},
1013     currentvalue: null,
1014     messageelt: null,
1015     throbber: null,
1016
1017     message: function(str, status, throbber) {
1018         var row;
1019         if (!this.messageelt) {
1020             row = new Element('tr');
1021             // we can't use row.update('<td></td><td><div></div></td>')
1022             // because gecko would mangle the <td>s
1023             row.insert(new Element('td'))
1024                .insert((new Element('td')).update(new Element('div')));
1025
1026             $("user_pseudo").up('tr').insert({after: row});
1027             this.messageelt = new Element('span');
1028             this.throbber = new Element("img", { src: "icons/throbber.gif"});
1029             row.down('div').insert(this.throbber).insert(this.messageelt);
1030         }
1031         if (throbber) {
1032             this.throbber.show();
1033         } else {
1034             this.throbber.hide();
1035         }
1036         this.messageelt.up().setStyle({visibility: ''});
1037         this.messageelt.className = status;
1038         this.messageelt.update(str);
1039     },
1040
1041     availableMessage: function(available) {
1042         var message = available ? SyjStrings.availablePseudo: SyjStrings.unavailablePseudo,
1043             status = available ? "success": "warn";
1044         this.message(message, status, false);
1045     },
1046
1047     reset: function() {
1048         if (this.req) {
1049             this.req.abort();
1050             this.req = this.currentvalue = null;
1051         }
1052         if (this.messageelt) {
1053             this.messageelt.up().setStyle({visibility: 'hidden'});
1054         }
1055     },
1056
1057     check: function() {
1058         var pseudo = $("user_pseudo").value;
1059
1060         this.reset();
1061
1062         if (!pseudo || !(pseudo.match(/^[a-zA-Z0-9_.]+$/))) {
1063             return;
1064         }
1065
1066         if (typeof this.exists[pseudo] === "boolean") {
1067             this.reset();
1068             this.availableMessage(!this.exists[pseudo]);
1069             return;
1070         }
1071
1072         this.message(SyjStrings.pseudoChecking, "", true);
1073
1074         this.currentvalue = pseudo;
1075         this.req = new Ajax.TimedRequest('userexists/' + encodeURIComponent(pseudo), 20, {
1076             onFailure: this.failure.bind(this),
1077             onSuccess: this.success.bind(this)
1078         });
1079     },
1080
1081     failure: function(transport) {
1082         var httpCode = 0, value = this.currentvalue;
1083
1084         if (transport) {
1085             httpCode = transport.getStatus();
1086         }
1087         this.reset();
1088         if (httpCode === 404) {
1089             this.exists[value] = false;
1090             this.availableMessage(true);
1091         }
1092
1093     },
1094
1095     success: function(transport) {
1096         var httpCode = transport.getStatus(), value = this.currentvalue;
1097         this.reset();
1098         this.exists[value] = true;
1099         this.availableMessage(false);
1100     }
1101 };
1102
1103 var Nominatim = (function() {
1104     var presubmit = function() {
1105         var input = $("nominatim-search");
1106         if (input.value.strip().empty()) {
1107             $("nominatim-message").setMessage(SyjStrings.notEmptyField, "warn");
1108             input.activate();
1109             return false;
1110         }
1111         $("nominatim-suggestions").hide();
1112         $("nominatim-message").hide();
1113         $("nominatim-throbber").show();
1114         return true;
1115     };
1116
1117     var zoomToExtent = function(bounds) { // we must call map.setCenter with forceZoomChange to true. See ol#2798
1118         var center = bounds.getCenterLonLat();
1119         if (this.baseLayer.wrapDateLine) {
1120             var maxExtent = this.getMaxExtent();
1121             bounds = bounds.clone();
1122             while (bounds.right < bounds.left) {
1123                 bounds.right += maxExtent.getWidth();
1124             }
1125             center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
1126         }
1127         this.setCenter(center, this.getZoomForExtent(bounds), false, true);
1128     };
1129
1130     var success = function(transport) {
1131         $("nominatim-throbber").hide();
1132
1133         if (!transport.responseJSON || !transport.responseJSON.length) {
1134             $("nominatim-message").setMessage(SyjStrings.noResult, 'error');
1135             $("nominatim-search").activate();
1136             return;
1137         }
1138
1139         var place = transport.responseJSON[0],
1140             bbox = place.boundingbox;
1141
1142         if (!bbox || bbox.length !== 4) {
1143             $("nominatim-message").setMessage(SyjStrings.requestError, 'error');
1144             return;
1145         }
1146
1147         extent = new OpenLayers.Bounds(bbox[2], bbox[1], bbox[3], bbox[0]).transform(WGS84, Mercator);
1148         zoomToExtent.call(SYJView.map, extent);
1149
1150         $("nominatim-suggestions-list").update();
1151
1152         var clickhandler = function(bbox) {
1153             return function(evt) {
1154                 evt.stop();
1155                 var extent = new OpenLayers.Bounds(bbox[2], bbox[1], bbox[3], bbox[0]).transform(WGS84, Mercator);
1156                 $("nominatim-suggestions-list").select("li").invoke('removeClassName', 'current');
1157                 evt.target.up('li').addClassName('current');
1158                 SYJView.map.zoomToExtent(extent);
1159             };
1160         };
1161
1162         var i;
1163         for (i = 0; i < transport.responseJSON.length; i++) {
1164             var item = transport.responseJSON[i];
1165             if (item.display_name && item.boundingbox && item.boundingbox.length === 4) {
1166                 var li = new Element("li");
1167                 var anchor = new Element("a", {
1168                     href: "",
1169                     className: "nominatim-suggestions-link"
1170                 });
1171
1172                 anchor.observe('click', clickhandler(item.boundingbox));
1173                 Element.text(anchor, item.display_name);
1174
1175                 var icon = new Element("img", {
1176                     className: "nominatim-suggestions-icon",
1177                     src: item.icon || 'icons/world.png'
1178                 });
1179                 li.insert(icon).insert(anchor);
1180                 $("nominatim-suggestions-list").insert(li);
1181                 if ($("nominatim-suggestions-list").childNodes.length >= 6) {
1182                     break;
1183                 }
1184             }
1185         }
1186
1187         if ($("nominatim-suggestions-list").childNodes.length > 1) {
1188             var bottomOffset = $('data_controls').measure('height') + 3;
1189             $("nominatim-suggestions").setStyle({
1190                 bottom: (document.viewport.getHeight() - $('data_controls').cumulativeOffset().top + 3).toString() + 'px'
1191             }).show();
1192             $("nominatim-suggestions-list").select("li:first-child")[0].addClassName('current');
1193         } else {
1194             $("nominatim-suggestions").hide();
1195         }
1196
1197     };
1198
1199     var failure = function(transport) {
1200         $("nominatim-throbber").hide();
1201
1202         var httpCode = 0, message = SyjStrings.unknownError, input; // default message error
1203
1204         if (transport) {
1205             httpCode = transport.getStatus();
1206         }
1207
1208         switch (httpCode) {
1209             case 0:
1210                 message = SyjStrings.notReachedError;
1211             break;
1212             case 400:
1213             case 404:
1214                 message = SyjStrings.requestError;
1215             break;
1216             case 500:
1217                 message = SyjStrings.serverError;
1218             break;
1219         }
1220
1221         $("nominatim-message").setMessage(message, 'error');
1222     };
1223
1224     return {
1225         init: function() {
1226             if (!$("nominatim-form")) {
1227                return;
1228             }
1229             $("nominatim-controls").hide();
1230             $("nominatim-label").observe('click', function(evt) {
1231                 $("nominatim-controls").show();
1232                 $("nominatim-search").activate();
1233                 evt.stop();
1234             });
1235
1236             $("nominatim-form").ajaxize({
1237                 presubmit: presubmit,
1238                 onSuccess: success,
1239                 onFailure: failure
1240               });
1241             new CloseBtn($("nominatim-suggestions"));
1242
1243             $$("#nominatim-message, #nominatim-suggestions, #nominatim-throbber").invoke('hide');
1244         }
1245     };
1246 }());
1247
1248 document.observe("dom:loaded", function() {
1249     SYJLogin.init();
1250     SYJUser.init();
1251     SYJDataUi.viewmode();
1252     SYJView.init();
1253     SYJNewpwd.init();
1254     LoginMgr.updateUI();
1255     Nominatim.init();
1256 });
1257
1258 window.onbeforeunload = function() {
1259     if (SYJView.unsavedRoute) {
1260         return SyjStrings.unsavedConfirmExit;
1261     } else {
1262         return undefined;
1263     }
1264 };