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