]> dev.renevier.net Git - syj.git/blobdiff - public/js/syj.js
merge linestrings for gpx containting multiple trkseg elements.
[syj.git] / public / js / syj.js
index e6f8537c3b8f4bf9db0748c8b787dc822343f906..8f5f509020fd0f6251055213aa3d050169f963fe 100644 (file)
@@ -1,25 +1,15 @@
-/*  This file is part of Syj, Copyright (c) 2010 Arnaud Renevier,
+/*  This file is part of Syj, Copyright (c) 2010-2011 Arnaud Renevier,
     and is published under the AGPL license. */
-Element.addMethods('input', {
-    observe : Element.Methods.observe.wrap(function(proceed, element, eventName, handler) {
-        if (eventName === "contentchange") {
-            proceed(element, 'keyup', function(evt) {
-                if (evt.keyCode == 13) {
-                    return;
-                }
-                handler.apply(null, arguments);
-            });
-            return proceed(element, 'change', handler);
-        }
-        return proceed(element, eventName, handler);
-    })
-});
+
+"use strict";
 
 // avoid openlayers alerts
 OpenLayers.Console.userError = function(error) {
     SYJView.messenger.setMessage(error, "error");
 };
 
+OpenLayers.Layer.Vector.prototype.renderers = ["SVG2", "VML", "Canvas"];
+
 var SyjSaveUI = {
     status: "unknown",
 
@@ -28,27 +18,14 @@ var SyjSaveUI = {
         return this;
     },
 
-    hide: function() {
-        $("geom_submit").blur();
-        $("geom_title").blur();
-        $("geomform").hide();
-        return this;
-    },
-
-    show: function() {
-        $("geomform").show();
-        return this;
-    },
-
     enable: function() {
         if (this.status === "enabled") {
             return this;
         }
         this.enableSubmit();
         $("geom_title").disabled = false;
-        $("geom_title").focus();
-        $("geom_title").select();
-        $("geomform").removeClassName("disabled");
+        $("geom_title").activate();
+        $$("#geom_accept_container, #geom_title_container").invoke('removeClassName', "disabled");
         this.status = "enabled";
         return this;
     },
@@ -60,13 +37,14 @@ var SyjSaveUI = {
         this.disableSubmit();
         $("geom_title").blur();
         $("geom_title").disabled = true;
-        $("geomform").addClassName("disabled");
+        $$("#geom_accept_container, #geom_title_container").invoke('addClassName', "disabled");
         this.status = "disabled";
         return this;
     },
 
     enableSubmit: function() {
         $("geom_submit").disabled = false;
+        $("geom_accept").disabled = false;
         this.status = "partial";
         return this;
     },
@@ -74,27 +52,92 @@ var SyjSaveUI = {
     disableSubmit: function() {
         $("geom_submit").blur();
         $("geom_submit").disabled = true;
+        $("geom_accept").blur();
+        $("geom_accept").disabled = true;
         this.status = "partial";
         return this;
     }
 };
 
-var SyjEditUI = {
-    init: function() {
-        return this;
-    },
+var SYJPathLength = (function(){
+    return {
+        update: function() {
+            var pathLength = 0, unit;
+            if (SYJView.mode === 'view') {
+                if (SYJView.viewLayer.features.length) {
+                    pathLength = SYJView.viewLayer.features[0].geometry.getGeodesicLength(Mercator);
+                }
+            } else {
+                pathLength = SYJView.editControl.handler.line.geometry.getGeodesicLength(Mercator);
+            }
 
-    hide: function() {
-        $("edit-btn").blur();
-        $("edit-btn").hide();
-        return this;
-    },
+            if (pathLength === 0) {
+                $("path-length").hide();
+                return;
+            }
+            $("path-length").show();
 
-    show: function() {
-        $("edit-btn").show();
-        return this;
-    }
-};
+            if (pathLength < 1000) {
+                // precision: 1 cm
+                pathLength = Math.round(pathLength * 100) / 100;
+                unit = 'm';
+            } else {
+                // precision: 1 m
+                pathLength = Math.round(pathLength) / 1000;
+                unit = 'km';
+            }
+            $("path-length-content").update(pathLength + ' ' + unit);
+        }
+    };
+}());
+
+var SYJDataUi = (function() {
+    var deck = null,
+        infotoggler = null,
+        getdeck = function() {
+            if (!deck) {
+                deck = new Deck("data_controls");
+            }
+            return deck;
+        },
+        getinfotoggler = function() {
+            if (!infotoggler) {
+                infotoggler = new Toggler('path-infos-content');
+                $("path-infos-toggler").insert({bottom: infotoggler.element});
+                var anchor = $("path-infos-anchor");
+                var parent = anchor.up('.menu-item');
+                if (parent) {
+                    anchor = parent;
+                }
+                anchor.observe('click', function(evt) {
+                    evt.stop();
+                    infotoggler.toggle(evt);
+                });
+                document.observe('toggler:open', function(evt) {
+                    if (evt.memo === infotoggler) {
+                        // XXX: update informations
+                    }
+                });
+            }
+            return infotoggler;
+        };
+    return {
+        viewmode: function() {
+            getdeck().setIndex(0);
+            if ($("path-infos")) {
+                getinfotoggler();
+                getinfotoggler().close();
+                $("path-infos").show();
+            }
+        },
+        editmode: function() {
+            getdeck().setIndex(1);
+            if ($("path-infos")) {
+                $("path-infos").hide();
+            }
+        }
+    };
+}());
 
 OpenLayers.Handler.SyjModifiablePath = OpenLayers.Class(OpenLayers.Handler.ModifiablePath, {
     mouseup: function(evt) {
@@ -229,9 +272,11 @@ var SYJView = {
     map: null,
     wkt: new OpenLayers.Format.WKT({ internalProjection: Mercator, externalProjection: WGS84 }),
     needsFormResubmit: false,
+    unsavedRoute: null,
+    mode: 'view',
 
     init: function() {
-        var externalGraphic, baseURL, baseLayer, layerOptions, extent = null, hidemessenger;
+        var externalGraphic, baseURL, baseLayer, layerOptions, hidemessenger;
 
         // is svg context, opera does not resolve links with base element is svg context
         externalGraphic = styleMap.edit.styles.select.defaultStyle.externalGraphic;
@@ -247,30 +292,54 @@ var SYJView = {
             theme: null
         });
 
-        baseLayer = new OpenLayers.Layer.OSM("OSM", null, { wrapDateLine: true , attribution: SyjStrings.osmAttribution });
+        baseLayer = new OpenLayers.Layer.OSM("OSM", [
+                'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
+                'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
+                'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'],
+                { attribution: SyjStrings.osmAttribution });
 
         layerOptions = {format:     OpenLayers.Format.WKT,
                         projection: WGS84,
-                        styleMap:   styleMap.view};
-        if (gLoggedInfo.ownername) {
-            layerOptions.attribution = SyjStrings.routeBy + ' ' + '<strong>' + gLoggedInfo.ownername + '</strong>';
-        }
+                        styleMap:   styleMap.view,
+                        attribution: SyjStrings.geomAttribution };
 
         this.viewLayer = new OpenLayers.Layer.Vector("View Layer", layerOptions);
         this.map.addLayers([baseLayer, this.viewLayer]);
 
-        $("edit-btn").observe('click', (function() {
-            this.messenger.hide();
-            this.editMode();
-        }).bind(this));
-        SyjEditUI.init().hide();
+        if ($("edit-btn")) {
+            $("edit-btn").observe('click', function() {
+                $("geom_submit").value = SyjStrings.editAction;
+                this.messenger.clearMessages();
+                this.editMode();
+                this.mode = 'edit';
+            }.bind(this));
+        }
+
+        if ($("create-btn")) {
+            $("create-btn").observe('click', function() {
+                $("geom_submit").value = SyjStrings.createAction;
+                this.messenger.clearMessages();
+                this.editMode();
+                this.mode = 'create';
+            }.bind(this));
+        }
+
+        if ($("clone-btn")) {
+            $("clone-btn").observe('click', function() {
+                $("geom_submit").value = SyjStrings.cloneAction;
+                $("geom_title").value = "";
+                this.messenger.clearMessages();
+                this.editMode();
+                this.mode = 'create';
+            }.bind(this));
+        }
 
         $("geomform").ajaxize({
                 presubmit: this.prepareForm.bind(this),
                 onSuccess: this.saveSuccess.bind(this),
                 onFailure: this.saveFailure.bind(this)
                 });
-        SyjSaveUI.init().hide();
+        SyjSaveUI.init();
 
         this.messenger = $('message');
         hidemessenger = this.messenger.empty();
@@ -283,31 +352,132 @@ var SYJView = {
             this.messenger.hide();
         }
 
-        extent = null;
-        if ($("geom_data").value) {
-            this.viewLayer.addFeatures([this.wkt.read($("geom_data").value)]);
-            extent = this.viewLayer.getDataExtent();
+        if (typeof gInitialGeom !== "undefined" && typeof gInitialGeom.data !== "undefined") {
+            this.viewLayer.addFeatures([this.wkt.read(gInitialGeom.data)]);
             // XXX: ie has not guessed height of map main div yet during map
             // initialisation. Now, it will read it correctly.
             this.map.updateSize();
-            SyjEditUI.show();
+            this.map.zoomToExtent(this.viewLayer.getDataExtent());
         } else {
-            extent = new OpenLayers.Bounds(gMaxExtent.minlon, gMaxExtent.minlat, gMaxExtent.maxlon, gMaxExtent.maxlat)
-                                         .transform(WGS84, Mercator);
-            this.editMode();
+            this.initMaPos(gInitialPos);
         }
-        this.map.zoomToExtent(extent);
+
+        $("map-overlay").hide();
+        $("geom_upload").observe('change', function(evt) {
+            var file = null, reader = null, readerror = null;
+            if (window.FileList && window.FileReader) {
+                file = evt.target.files[0];
+                reader = new FileReader();
+                readerror = function() {
+                    this.messenger.setMessage(SyjStrings.uploadFileError, "warn");
+                }.bind(this);
+                reader.onload = function(evt) {
+                    var data = null, results = null, engine = null, vector = null, i = 0, format = null, formats = ['KML', 'GPX', 'GeoJSON'];
+
+                    $("geom_upload_container").removeClassName("disabled");
+                    $("geom_upload").disabled = false;
+                    if (evt.error) {
+                        readerror();
+                        return;
+                    }
+                    data = evt.target.result;
+
+                    for (i = 0; i < formats.length; i++) {
+                        format = formats[i];
+                        engine = new OpenLayers.Format[format]({ internalProjection: Mercator, externalProjection: WGS84 });
+                        try {
+                            results = engine.read(data);
+                        } catch(e) {
+                        }
+                        if (results && results.length) {
+                            break;
+                        }
+                    }
+                    if (!results || !results.length) {
+                        readerror();
+                        return;
+                    }
+
+                    vector = results[0];
+                    if (vector.geometry.CLASS_NAME !== "OpenLayers.Geometry.LineString") {
+                        readerror();
+                        return;
+                    }
+                    // merge linestrings for gpx containting multiple trkseg elements.
+                    if (format === 'GPX') {
+                      for (i = 1; i < results.length; i++) {
+                        vector.geometry.addComponents(results[i].geometry.components);
+                      }
+                    }
+                    this.viewLayer.addFeatures([vector]);
+                    this.map.zoomToExtent(this.viewLayer.getDataExtent());
+
+                    if ($("edit-btn")) {
+                        $("edit-btn").click();
+                    } else if ($("create-btn")) {
+                        $("create-btn").click();
+                    }
+
+                    if (this.editControl.handler.realPoints.length < 2) {
+                        SyjSaveUI.disable();
+                    } else {
+                       SyjSaveUI.enable();
+                    }
+
+                    if (vector.data && vector.data.name) {
+                        $("geom_title").value = vector.data.name;
+                    }
+                }.bind(this);
+                $("geom_upload_container").addClassName("disabled");
+                $("geom_upload").disabled = true;
+                reader.readAsText(file);
+                return;
+            }
+            $("map-overlay").show();
+            SyjSaveUI.enable();
+            this.editControl.deactivate();
+        }.bind(this));
+
         document.observe('simplebox:shown', this.observer.bindAsEventListener(this));
+        SYJPathLength.update();
+    },
+
+    initMaPos: function (aPos) {
+        var extent = null, center = null, zoom = 0;
+
+        if (aPos.hasOwnProperty('lon') && aPos.hasOwnProperty('lat') && aPos.hasOwnProperty('zoom')) {
+            center = new OpenLayers.LonLat(parseFloat(aPos.lon), parseFloat(aPos.lat)).transform(WGS84, Mercator);
+            zoom = parseInt(aPos.zoom, 10);
+        } else if (aPos.hasOwnProperty('minlon') && aPos.hasOwnProperty('minlat')
+                    && aPos.hasOwnProperty('maxlon') && aPos.hasOwnProperty('maxlat')) {
+            extent = new OpenLayers.Bounds(aPos.minlon, aPos.minlat, aPos.maxlon, aPos.maxlat)
+                                         .transform(WGS84, Mercator);
+        } else {
+            extent = new OpenLayers.Bounds(-160, -70, 160, 70).transform(WGS84, Mercator);
+        }
+
+        if (extent) {
+            this.map.zoomToExtent(extent);
+        } else {
+            this.map.setCenter(center, zoom);
+        }
     },
 
     observer: function(evt) {
         if (evt.eventName === "simplebox:shown" && evt.memo.element !== $("termsofusearea")) {
-            this.messenger.hide();
+            this.messenger.clearMessages();
         }
     },
 
     prepareForm: function(form) {
-        var line, realPoints, idx, handler;
+        if (!LoginMgr.logged && !$("geom_accept").checked) {
+            this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
+            $("geom_accept_container").highlight('#F08080');
+            $("geom_accept").activate();
+            return false;
+        }
+
+        var line, realPoints, idx;
 
         line = new OpenLayers.Geometry.LineString();
         realPoints = this.editControl.handler.realPoints;
@@ -315,18 +485,34 @@ var SYJView = {
             line.addComponent(realPoints[idx].geometry.clone());
         }
         this.viewLayer.addFeatures(new OpenLayers.Feature.Vector(line));
-        handler = this.editControl.handler;
+
+        this.viewMode();
+
+        if (line.components.length) {
+            $("geom_data").value = this.wkt.write(new OpenLayers.Feature.Vector(line));
+        } else {
+            $("geom_data").value = "";
+        }
+
+        if (this.mode === "edit" && typeof gLoggedInfo.pathid !== "undefined") {
+            $("geomform").setAttribute("action", "path/" + gLoggedInfo.pathid.toString() + '/update');
+        } else {
+            $("geomform").setAttribute("action", "path");
+        }
+        this.needsFormResubmit = false;
+        SyjSaveUI.disable.bind(SyjSaveUI).defer();
+        this.messenger.clearMessages();
+        return true;
+    },
+
+    viewMode: function() {
+        var handler = this.editControl.handler;
         OpenLayers.Handler.ModifiablePath.prototype.finalize.apply(handler, arguments);
         // we need to recreate them on next createFeature; otherwise
         // they'll reference destroyed features
         delete(handler.handlers.drag);
         delete(handler.handlers.feature);
         this.editControl.deactivate();
-
-        $("geom_data").value = this.wkt.write(new OpenLayers.Feature.Vector(line));
-        this.needsFormResubmit = false;
-        SyjSaveUI.disable.bind(SyjSaveUI).defer();
-        this.messenger.hide();
     },
 
     editMode: function() {
@@ -349,15 +535,19 @@ var SYJView = {
                 }
                 this.editControl.handler.addPoints(pixels);
             }
+            this.unsavedRoute = {
+                features: this.viewLayer.features.invoke('clone'),
+                title: $("geom_title").value
+            };
         }
 
         this.viewLayer.destroyFeatures();
 
-        SyjEditUI.hide();
+        SYJDataUi.editmode();
         if (this.editControl.handler.realPoints && this.editControl.handler.realPoints.length >= 2) {
-            SyjSaveUI.show().disableSubmit();
+            SyjSaveUI.disableSubmit();
         } else {
-            SyjSaveUI.show().disable();
+            SyjSaveUI.disable();
         }
     },
 
@@ -371,12 +561,28 @@ var SYJView = {
         this.editControl = new OpenLayers.Control.DrawFeature(new OpenLayers.Layer.Vector(), OpenLayers.Handler.SyjModifiablePath, {
             callbacks: {
                 modify: function(f, line) {
-                    if (this.handler.realPoints.length < 2) {
-                        SyjSaveUI.show().disable();
+                    SYJPathLength.update();
+
+                    var npoints = this.handler.realPoints.length;
+                    if (npoints === 0) {
+                        $("geom_upload_container").show();
+                        SYJView.unsavedRoute = null;
                     } else {
-                        SyjSaveUI.show().enable();
+                        if (!SYJView.unsavedRoute) {
+                            SYJView.unsavedRoute = {};
+                        }
                     }
-                }
+
+                    if (npoints < 2) {
+                        SyjSaveUI.disable();
+                    } else {
+                        SyjSaveUI.enable();
+                    }
+                },
+                create: function(f, line) {
+                    this.messenger.clearMessages();
+                    $("geom_upload_container").hide();
+                }.bind(this)
             },
 
             handlerOptions: {
@@ -394,54 +600,46 @@ var SYJView = {
     },
 
     saveSuccess: function(transport) {
-      if (!$("geom_id").value) {
-          location = "idx/" + transport.responseText;
+      // server sends and empty response on success. If we get a response, that
+      // probably means an error or warning has been printed by server.
+      if (!transport.responseJSON && transport.responseText.length) {
+          this.saveFailure(null, 500);
+          return;
+      }
+
+      this.unsavedRoute = null;
+      if (transport.responseJSON && (typeof transport.responseJSON.redirect === "string")) {
+          location = transport.responseJSON.redirect;
           return;
       }
-      this.messenger.setMessage(SyjStrings.saveSuccess, "success");
 
-      SyjSaveUI.hide();
-      SyjEditUI.show();
+      this.messenger.setMessage(SyjStrings.saveSuccess, "success");
+      SYJDataUi.viewmode();
       document.title = $('geom_title').value;
     },
 
-    saveFailure: function(transport) {
-        var httpCode = 0, message = "";
-
-        if (transport) {
-            httpCode = transport.getStatus();
+    saveFailure: function(transport, httpCode) {
+        var message = "";
+        if (typeof httpCode === "undefined") {
+            httpCode = transport? transport.getStatus(): 0;
         }
-        message = "";
+
         switch (httpCode) {
             case 0:
                 message = SyjStrings.notReachedError;
             break;
             case 400:
             case 404:
-            case 410:
-                message = SyjStrings.requestError; // default message
-                if (transport.responseJSON) {
-                    switch (transport.responseJSON.message) {
-                        case "unreferenced":
-                            message = SyjStrings.unreferencedError;
-                        break;
-                        case "uniquepath":
-                            message = SyjStrings.uniquePathError;
-                        break;
-                        default:
-                        break;
-                    }
-                }
+                message = SyjStrings.requestError;
             break;
             case 403:
                 message = "";
-                this.needsFormResubmit = true;
-                if (loginMgr.hasAlreadyConnected()) {
-                    SYJLogin.messenger.setMessage(SyjStrings.cookiesNeeded, "warn");
-                } else {
-                    SYJLogin.messenger.setMessage(SyjStrings.loginNeeded, "warn");
-                }
+                SYJLogin.messenger.setMessage(SyjStrings.loginNeeded, "warn");
                 SYJLogin.modalbox.show();
+                this.needsFormResubmit = true;
+            break;
+            case 410:
+                message = SyjStrings.gonePathError;
             break;
             case 500:
                 message = SyjStrings.serverError;
@@ -473,7 +671,12 @@ var SYJModalClass = Class.create({
             closeMethods: ["onescapekey", "onouterclick", "onbutton"]
         });
 
-        $(this.type + "_control_anchor").observe("click", function(evt) {
+        var anchor = $(this.type + '_control_anchor');
+        var parent = anchor.up('.menu-item');
+        if (parent) {
+            anchor = parent;
+        }
+        anchor.observe("click", function(evt) {
             this.modalbox.show();
             evt.stop();
         }.bindAsEventListener(this));
@@ -491,7 +694,7 @@ var SYJModalClass = Class.create({
     checkNotEmpty: function(input, message) {
         if ($(input).value.strip().empty()) {
             this.messenger.setMessage(message, "warn");
-            $(input).highlight('#F08080').focus();
+            $(input).highlight('#F08080').activate();
             return false;
         }
         return true;
@@ -505,9 +708,8 @@ var SYJModalClass = Class.create({
             if (simplebox === this.modalbox) {
                 input = this.area.select('input[type="text"]')[0];
                 (function () {
-                    input.focus();
-                    input.select();
-                }).defer();
+                    input.activate();
+                }.defer());
             } else {
                 this.modalbox.hide();
             }
@@ -543,12 +745,11 @@ var SYJModalClass = Class.create({
 
         this.messenger.setMessage(message, "error");
         input = this.area.select('input[type="text"]')[0];
-        input.highlight('#F08080').focus();
-        input.select();
+        input.highlight('#F08080').activate();
     },
 
     reset: function() {
-        this.messenger.hide();
+        this.messenger.clearMessages();
         this.area.select('.message').invoke('setMessageStatus', null);
     }
 });
@@ -561,17 +762,27 @@ var SYJUserClass = Class.create(SYJModalClass, {
         $super();
         $("termsofusearea").hide();
 
-        $("user_termsofuse_anchor").observe("click", function(evt) {
+        var touevt = (function(evt) {
+            if (evt.type === "keyup" && evt.keyCode !== 32) { // 32 = space
+                // allow opening box by pressing space
+                return;
+            }
             if (!this.toubox) {
-                $("termsofusearea").show();
-                $("termsofuseiframe").setAttribute("src", evt.target.href);
                 this.toubox = new SimpleBox($("termsofusearea"), {
                     closeMethods: ["onescapekey", "onouterclick", "onbutton"]
                 });
             }
             this.toubox.show();
+            if (!$("termsofuseiframe").getAttribute("src")) {
+                $("termsofusearea").show();
+                $("termsofuseiframe").setAttribute("src", evt.target.href);
+            }
             evt.stop();
-        }.bindAsEventListener(this));
+        }).bindAsEventListener(this);
+
+        ["click", "keyup"].each(function (evtName) {
+            $$("#user_termsofuse_anchor, #geom_termsofuse_anchor").invoke('observe', evtName, touevt);
+        })
 
         $$("#login_area_create > a").invoke('observe', 'click',
             function(evt) {
@@ -579,6 +790,19 @@ var SYJUserClass = Class.create(SYJModalClass, {
                 evt.stop();
             }.bindAsEventListener(this));
 
+        $("user_pseudo-desc").hide();
+        $("user_pseudo").observe('contentchange', function(evt) {
+            var value = evt.target.value;
+            PseudoChecker.reset();
+            if (value && !(value.match(/^[a-zA-Z0-9_.]+$/))) {
+                $("user_pseudo-desc").show().setMessageStatus("warn");
+            } else {
+                $("user_pseudo-desc").hide();
+            }
+        }).timedobserve(function() {
+            PseudoChecker.check();
+        });
+
         $("user_password").observe('contentchange', function(evt) {
             if (evt.target.value.length < 6) {
                 $("user_password-desc").setMessageStatus("warn");
@@ -586,17 +810,26 @@ var SYJUserClass = Class.create(SYJModalClass, {
                 $("user_password-desc").setMessageStatus("success");
             }
         }.bindAsEventListener(this));
+
+        $('account-create-anchor').insert({after: new Toggler('account-info').element});
     },
 
     presubmit: function() {
+        this.messenger.clearMessages();
+        PseudoChecker.reset();
         if (!(this.checkNotEmpty("user_pseudo", SyjStrings.userEmptyWarn))) {
             return false;
         }
 
         if (!($("user_pseudo").value.match(/^[a-zA-Z0-9_.]+$/))) {
-            this.messenger.setMessage(SyjStrings.invalidPseudo, "warn");
-            $("user_pseudo").highlight('#F08080').focus();
-            $("user_pseudo").select();
+            $("user_pseudo-desc").show().setMessageStatus("warn");
+            $("user_pseudo").highlight('#F08080').activate();
+            return false;
+        }
+
+        if (PseudoChecker.exists[$("user_pseudo").value]) {
+            PseudoChecker.availableMessage(false);
+            $("user_pseudo").highlight('#F08080').activate();
             return false;
         }
 
@@ -606,15 +839,13 @@ var SYJUserClass = Class.create(SYJModalClass, {
 
         if ($("user_password").value.length < 6) {
             $("user_password-desc").setMessageStatus("warn");
-            $("user_password").highlight('#F08080').focus();
-            $("user_password").select();
+            $("user_password").highlight('#F08080').activate();
             return false;
         }
 
         if ($("user_password").value !== $("user_password_confirm").value) {
             this.messenger.setMessage(SyjStrings.passwordNoMatchWarn, "warn");
-            $("user_password").highlight('#F08080').focus();
-            $("user_password").select();
+            $("user_password").highlight('#F08080').activate();
             return false;
         }
 
@@ -624,7 +855,8 @@ var SYJUserClass = Class.create(SYJModalClass, {
 
         if (!$("user_accept").checked) {
             this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
-            $("user_accept").highlight('#F08080').focus();
+            $("user_accept_container").highlight('#F08080');
+            $("user_accept").activate();
             return false;
         }
 
@@ -633,12 +865,19 @@ var SYJUserClass = Class.create(SYJModalClass, {
     },
 
     success: function(transport) {
-        loginMgr.login();
+        if (!transport.responseJSON ||
+            typeof transport.responseJSON.pseudo !== "string"
+            ) {
+            this.messenger.setMessage(SyjStrings.unknownError, "error");
+            return;
+        }
+
+        LoginMgr.login(transport.responseJSON.pseudo);
         SYJView.messenger.setMessage(SyjStrings.userSuccess, "success");
         this.modalbox.hide();
         if (SYJView.needsFormResubmit) {
             SYJView.messenger.addMessage(SyjStrings.canResubmit);
-            $("geom_submit").focus();
+            $("geom_submit").activate();
         }
     },
 
@@ -661,7 +900,7 @@ var SYJUserClass = Class.create(SYJModalClass, {
                             focusInput = $("user_email");
                         break;
                         case "uniquepseudo":
-                            message = SyjStrings.uniqueUserError;
+                            PseudoChecker.availableMessage(false);
                             focusInput = $("user_pseudo");
                         break;
                         case "uniqueemail":
@@ -673,12 +912,11 @@ var SYJUserClass = Class.create(SYJModalClass, {
             break;
         }
 
-        if (message) {
-            this.messenger.setMessage(message, "error");
-            if (focusInput) {
-                focusInput.highlight('#F08080').focus();
-                focusInput.select();
+        if (focusInput) {
+            if (message) {
+                this.messenger.setMessage(message, "error");
             }
+            focusInput.highlight('#F08080').activate();
             return;
         }
 
@@ -695,6 +933,7 @@ var SYJLoginClass = Class.create(SYJModalClass, {
     },
 
     presubmit: function() {
+        this.messenger.clearMessages();
         if (!(this.checkNotEmpty("login_user", SyjStrings.userEmptyWarn))) {
             return false;
         }
@@ -704,16 +943,20 @@ var SYJLoginClass = Class.create(SYJModalClass, {
     },
 
     success: function(transport) {
-        if (transport.responseText === "1") {
-            loginMgr.login(true);
-        } else {
-            loginMgr.login();
+        if (!transport.responseJSON ||
+            typeof transport.responseJSON.iscreator !== "boolean" ||
+            typeof transport.responseJSON.pseudo !== "string"
+            ) {
+            this.messenger.setMessage(SyjStrings.unknownError, "error");
+            return;
         }
+        LoginMgr.login(transport.responseJSON.pseudo, transport.responseJSON.iscreator);
+
         SYJView.messenger.setMessage(SyjStrings.loginSuccess, "success");
         this.modalbox.hide();
         if (SYJView.needsFormResubmit) {
             SYJView.messenger.addMessage(SyjStrings.canResubmit);
-            $("geom_submit").focus();
+            $("geom_submit").activate();
         }
     },
 
@@ -737,8 +980,7 @@ var SYJLoginClass = Class.create(SYJModalClass, {
         if (message) {
             this.messenger.setMessage(message, "error");
             if (focusInput) {
-                focusInput.highlight('#F08080').focus();
-                focusInput.select();
+                focusInput.highlight('#F08080').activate();
             }
             return;
         }
@@ -766,7 +1008,7 @@ var SYJNewpwdClass = Class.create(SYJModalClass, {
 });
 var SYJNewpwd = new SYJNewpwdClass();
 
-var loginMgr = Object.extend(gLoggedInfo, {
+var LoginMgr = Object.extend(gLoggedInfo, {
     controlsdeck: null,
 
     updateUI: function() {
@@ -775,37 +1017,291 @@ var loginMgr = Object.extend(gLoggedInfo, {
         }
         if (this.logged) {
             this.controlsdeck.setIndex(1);
+            $$(".logged-hide").invoke('hide');
+            $$(".logged-show").invoke('show');
         } else {
             this.controlsdeck.setIndex(0);
+            $$(".logged-hide").invoke('show');
+            $$(".logged-show").invoke('hide');
         }
 
-        if (this.isowner) {
-            $("data_controls").show();
-        } else {
-            $("data_controls").hide();
+        if ($("edit-btn")) {
+            if (this.iscreator && SYJView.mode === 'view') {
+                $("edit-btn").show();
+            } else {
+                $("edit-btn").hide();
+            }
         }
     },
 
-    login: function(aIsOwner) {
-        if (typeof aIsOwner === "boolean") {
-            this.isowner = aIsOwner;
+    login: function(aPseudo, aIsCreator) {
+        if (typeof aIsCreator === "boolean") {
+            this.iscreator = aIsCreator;
         }
         this.logged = true;
-        this.connections++;
+        $$('.logged-pseudo').each(function(elt) {
+            $A(elt.childNodes).filter(function(node) {
+                return (node.nodeType === 3 || node.tagName.toLowerCase() === 'br');
+            }).each(function(node) {
+                node.nodeValue = node.nodeValue.replace('%s', aPseudo);
+            });
+        });
         this.updateUI();
+    }
+});
+
+var PseudoChecker = {
+    req: null,
+    exists: {},
+    currentvalue: null,
+    messageelt: null,
+    throbber: null,
+
+    message: function(str, status, throbber) {
+        var row;
+        if (!this.messageelt) {
+            row = new Element('tr');
+            // we can't use row.update('<td></td><td><div></div></td>')
+            // because gecko would mangle the <td>s
+            row.insert(new Element('td'))
+               .insert((new Element('td')).update(new Element('div')));
+
+            $("user_pseudo").up('tr').insert({after: row});
+            this.messageelt = new Element('span');
+            this.throbber = new Element("img", { src: "icons/throbber.gif"});
+            row.down('div').insert(this.throbber).insert(this.messageelt);
+        }
+        if (throbber) {
+            this.throbber.show();
+        } else {
+            this.throbber.hide();
+        }
+        this.messageelt.up().setStyle({visibility: ''});
+        this.messageelt.className = status;
+        this.messageelt.update(str);
+    },
+
+    availableMessage: function(available) {
+        var message = available ? SyjStrings.availablePseudo: SyjStrings.unavailablePseudo,
+            status = available ? "success": "warn";
+        this.message(message, status, false);
+    },
+
+    reset: function() {
+        if (this.req) {
+            this.req.abort();
+            this.req = this.currentvalue = null;
+        }
+        if (this.messageelt) {
+            this.messageelt.up().setStyle({visibility: 'hidden'});
+        }
+    },
+
+    check: function() {
+        var pseudo = $("user_pseudo").value;
+
+        this.reset();
+
+        if (!pseudo || !(pseudo.match(/^[a-zA-Z0-9_.]+$/))) {
+            return;
+        }
+
+        if (typeof this.exists[pseudo] === "boolean") {
+            this.reset();
+            this.availableMessage(!this.exists[pseudo]);
+            return;
+        }
+
+        this.message(SyjStrings.pseudoChecking, "", true);
+
+        this.currentvalue = pseudo;
+        this.req = new Ajax.TimedRequest('userexists/' + encodeURIComponent(pseudo), 20, {
+            onFailure: this.failure.bind(this),
+            onSuccess: this.success.bind(this)
+        });
+    },
+
+    failure: function(transport) {
+        var httpCode = 0, value = this.currentvalue;
+
+        if (transport) {
+            httpCode = transport.getStatus();
+        }
+        this.reset();
+        if (httpCode === 404) {
+            this.exists[value] = false;
+            this.availableMessage(true);
+        }
+
     },
 
-    // needed in case of 403 errors: if user has already connected successfully
-    // before, it probably means he has cookies disabled
-    hasAlreadyConnected: function() {
-        return (this.connections >= 1);
+    success: function(transport) {
+        var httpCode = transport.getStatus(), value = this.currentvalue;
+        this.reset();
+        this.exists[value] = true;
+        this.availableMessage(false);
     }
-});
+};
+
+var Nominatim = (function() {
+    var presubmit = function() {
+        var input = $("nominatim-search");
+        if (input.value.strip().empty()) {
+            $("nominatim-message").setMessage(SyjStrings.notEmptyField, "warn");
+            input.activate();
+            return false;
+        }
+        $("nominatim-suggestions").hide();
+        $("nominatim-message").hide();
+        $("nominatim-throbber").show();
+        return true;
+    };
+
+    var zoomToExtent = function(bounds) { // we must call map.setCenter with forceZoomChange to true. See ol#2798
+        var center = bounds.getCenterLonLat();
+        if (this.baseLayer.wrapDateLine) {
+            var maxExtent = this.getMaxExtent();
+            bounds = bounds.clone();
+            while (bounds.right < bounds.left) {
+                bounds.right += maxExtent.getWidth();
+            }
+            center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+        }
+        this.setCenter(center, this.getZoomForExtent(bounds), false, true);
+    };
+
+    var success = function(transport) {
+        $("nominatim-throbber").hide();
+
+        if (!transport.responseJSON || !transport.responseJSON.length) {
+            $("nominatim-message").setMessage(SyjStrings.noResult, 'error');
+            $("nominatim-search").activate();
+            return;
+        }
+
+        var place = transport.responseJSON[0],
+            bbox = place.boundingbox;
+
+        if (!bbox || bbox.length !== 4) {
+            $("nominatim-message").setMessage(SyjStrings.requestError, 'error');
+            return;
+        }
+
+        extent = new OpenLayers.Bounds(bbox[2], bbox[1], bbox[3], bbox[0]).transform(WGS84, Mercator);
+        zoomToExtent.call(SYJView.map, extent);
+
+        $("nominatim-suggestions-list").update();
+
+        var clickhandler = function(bbox) {
+            return function(evt) {
+                evt.stop();
+                var extent = new OpenLayers.Bounds(bbox[2], bbox[1], bbox[3], bbox[0]).transform(WGS84, Mercator);
+                $("nominatim-suggestions-list").select("li").invoke('removeClassName', 'current');
+                evt.target.up('li').addClassName('current');
+                SYJView.map.zoomToExtent(extent);
+            };
+        };
+
+        var i;
+        for (i = 0; i < transport.responseJSON.length; i++) {
+            var item = transport.responseJSON[i];
+            if (item.display_name && item.boundingbox && item.boundingbox.length === 4) {
+                var li = new Element("li");
+                var anchor = new Element("a", {
+                    href: "",
+                    className: "nominatim-suggestions-link"
+                });
+
+                anchor.observe('click', clickhandler(item.boundingbox));
+                Element.text(anchor, item.display_name);
+
+                var icon = new Element("img", {
+                    className: "nominatim-suggestions-icon",
+                    src: item.icon || 'icons/world.png'
+                });
+                li.insert(icon).insert(anchor);
+                $("nominatim-suggestions-list").insert(li);
+                if ($("nominatim-suggestions-list").childNodes.length >= 6) {
+                    break;
+                }
+            }
+        }
+
+        if ($("nominatim-suggestions-list").childNodes.length > 1) {
+            var bottomOffset = $('data_controls').measure('height') + 3;
+            $("nominatim-suggestions").setStyle({
+                bottom: (document.viewport.getHeight() - $('data_controls').cumulativeOffset().top + 3).toString() + 'px'
+            }).show();
+            $("nominatim-suggestions-list").select("li:first-child")[0].addClassName('current');
+        } else {
+            $("nominatim-suggestions").hide();
+        }
+
+    };
+
+    var failure = function(transport) {
+        $("nominatim-throbber").hide();
+
+        var httpCode = 0, message = SyjStrings.unknownError, input; // default message error
+
+        if (transport) {
+            httpCode = transport.getStatus();
+        }
+
+        switch (httpCode) {
+            case 0:
+                message = SyjStrings.notReachedError;
+            break;
+            case 400:
+            case 404:
+                message = SyjStrings.requestError;
+            break;
+            case 500:
+                message = SyjStrings.serverError;
+            break;
+        }
+
+        $("nominatim-message").setMessage(message, 'error');
+    };
+
+    return {
+        init: function() {
+            if (!$("nominatim-form")) {
+               return;
+            }
+            $("nominatim-controls").hide();
+            $("nominatim-label").observe('click', function(evt) {
+                $("nominatim-controls").show();
+                $("nominatim-search").activate();
+                evt.stop();
+            });
+
+            $("nominatim-form").ajaxize({
+                presubmit: presubmit,
+                onSuccess: success,
+                onFailure: failure
+              });
+            new CloseBtn($("nominatim-suggestions"));
+
+            $$("#nominatim-message, #nominatim-suggestions, #nominatim-throbber").invoke('hide');
+        }
+    };
+}());
 
 document.observe("dom:loaded", function() {
     SYJLogin.init();
     SYJUser.init();
+    SYJDataUi.viewmode();
     SYJView.init();
     SYJNewpwd.init();
-    loginMgr.updateUI();
+    LoginMgr.updateUI();
+    Nominatim.init();
 });
+
+window.onbeforeunload = function() {
+    if (SYJView.unsavedRoute) {
+        return SyjStrings.unsavedConfirmExit;
+    } else {
+        return undefined;
+    }
+};