]> dev.renevier.net Git - syj.git/blob - public/js/syj.js
some jslint cleanup
[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     hide: function() {
18         $("geom_submit").blur();
19         $("geom_title").blur();
20         $("geomform").hide();
21         return this;
22     },
23
24     show: function() {
25         $("geomform").show();
26         return this;
27     },
28
29     enable: function() {
30         if (this.status === "enabled") {
31             return this;
32         }
33         this.enableSubmit();
34         $("geom_title").disabled = false;
35         $("geom_title").activate();
36         $("geomform").removeClassName("disabled");
37         this.status = "enabled";
38         return this;
39     },
40
41     disable: function() {
42         if (this.status === "disabled") {
43             return this;
44         }
45         this.disableSubmit();
46         $("geom_title").blur();
47         $("geom_title").disabled = true;
48         $("geomform").addClassName("disabled");
49         this.status = "disabled";
50         return this;
51     },
52
53     enableSubmit: function() {
54         $("geom_submit").disabled = false;
55         this.status = "partial";
56         return this;
57     },
58
59     disableSubmit: function() {
60         $("geom_submit").blur();
61         $("geom_submit").disabled = true;
62         this.status = "partial";
63         return this;
64     }
65 };
66
67 var SyjEditUI = {
68     hide: function() {
69         $("data_controls_btns").blur();
70         $("data_controls_btns").hide();
71         return this;
72     },
73
74     show: function() {
75         $("data_controls_btns").show();
76         return this;
77     }
78 };
79
80 OpenLayers.Handler.SyjModifiablePath = OpenLayers.Class(OpenLayers.Handler.ModifiablePath, {
81     mouseup: function(evt) {
82         // do not add a point when navigating
83         var mapControls = this.control.map.controls, idx, ctrl;
84
85         for (idx = mapControls.length; idx-->0; ) {
86             ctrl = mapControls[idx];
87             if (this.isCtrlNavigationActive(ctrl, evt)) {
88                 return true;
89             }
90         }
91         return OpenLayers.Handler.ModifiablePath.prototype.mouseup.apply(this, arguments);
92     },
93
94     addPoints: function(pixel) {
95         // redraw before last point. As we have a special style for last point, we
96         // need to redraw before last point after adding a new point (otherwise, it
97         // keeps special style forever)
98         var oldpoint = this.point;
99         OpenLayers.Handler.ModifiablePath.prototype.addPoints.apply(this, arguments);
100         this.layer.drawFeature(oldpoint);
101     },
102
103     isCtrlNavigationActive: function(ctrl, evt) {
104         var tolerance = 4, xDiff, yDiff;
105
106         if (!(ctrl instanceof OpenLayers.Control.Navigation)) {
107             return false;
108         }
109
110         if (ctrl.zoomBox &&
111             ctrl.zoomBox.active &&
112             ctrl.zoomBox.handler &&
113             ctrl.zoomBox.handler.active &&
114             ctrl.zoomBox.handler.dragHandler &&
115             ctrl.zoomBox.handler.dragHandler.start) {
116             return true;
117         }
118
119         if (!ctrl.dragPan ||
120             !ctrl.dragPan.active ||
121             !ctrl.dragPan.handler ||
122             !ctrl.dragPan.handler.started ||
123             !ctrl.dragPan.handler.start) {
124             return false;
125         }
126
127         // if mouse moved 4 or less pixels, consider it has not moved.
128         tolerance = 4;
129
130         xDiff = evt.xy.x - ctrl.dragPan.handler.start.x;
131         yDiff = evt.xy.y - ctrl.dragPan.handler.start.y;
132
133         if (Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2)) <= tolerance) {
134             return false;
135         }
136         return true;
137     },
138
139     finalize: function(cancel) {
140         // do nothing. We don't want to finalize path
141     }
142 });
143
144 var styleMap = {
145     edit: new OpenLayers.StyleMap({
146         "default": new OpenLayers.Style({
147             pointRadius: "${radius}", // sized according to type attribute
148             fillColor: "#ffcc66",
149             strokeColor: "#ff9933",
150             strokeWidth: 2,
151             strokeOpacity: "${opacity}",
152             fillOpacity: "${opacity}"
153         },
154         {
155             context: {
156                 radius: function(feature) {
157                     var features;
158
159                     if (!(feature.geometry instanceof OpenLayers.Geometry.Point)) {
160                         return 0;
161                     }
162                     if (feature.type === "middle") {
163                         return 4;
164                     }
165                     features = feature.layer.features;
166                     if (OpenLayers.Util.indexOf(features, feature) === 0) {
167                         return 5;
168                     } else if (OpenLayers.Util.indexOf(features, feature) === features.length - 1) {
169                         return 5;
170                     }
171                     return 3;
172                 },
173                 opacity: function (feature) {
174                     if (feature.type === "middle") {
175                         return 0.5;
176                     } else {
177                         return 1;
178                     }
179                 }
180             }
181         }),
182
183         "select": new OpenLayers.Style({
184             externalGraphic: "icons/delete.png",
185             graphicHeight: 16
186         }),
187
188         "select_for_canvas": new OpenLayers.Style({
189             strokeColor: "blue",
190             fillColor: "lightblue"
191         })
192
193     }),
194
195     view: new OpenLayers.StyleMap({
196         "default": new OpenLayers.Style({
197             strokeColor: "blue",
198             strokeWidth: 5,
199             strokeOpacity: 0.7
200         })
201     })
202 };
203
204 var WGS84 = new OpenLayers.Projection("EPSG:4326");
205 var Mercator = new OpenLayers.Projection("EPSG:900913");
206
207 var SYJView = {
208     viewLayer: null,
209     editControl: null,
210     map: null,
211     wkt: new OpenLayers.Format.WKT({ internalProjection: Mercator, externalProjection: WGS84 }),
212     needsFormResubmit: false,
213     unsavedRoute: null,
214     mode: 'view',
215
216     init: function() {
217         var externalGraphic, baseURL, baseLayer, layerOptions, extent, hidemessenger;
218
219         // is svg context, opera does not resolve links with base element is svg context
220         externalGraphic = styleMap.edit.styles.select.defaultStyle.externalGraphic;
221         baseURL = document.getElementsByTagName("base")[0].href;
222         styleMap.edit.styles.select.defaultStyle.externalGraphic = baseURL + externalGraphic;
223
224         this.map = new OpenLayers.Map('map', {
225             controls: [
226                 new OpenLayers.Control.Navigation(),
227                 new OpenLayers.Control.PanZoom(),
228                 new OpenLayers.Control.Attribution()
229             ],
230             theme: null
231         });
232
233         baseLayer = new OpenLayers.Layer.OSM("OSM", null, { wrapDateLine: true , attribution: SyjStrings.osmAttribution });
234
235         layerOptions = {format:     OpenLayers.Format.WKT,
236                         projection: WGS84,
237                         styleMap:   styleMap.view};
238         if (gLoggedInfo.creatorname) {
239             layerOptions.attribution = SyjStrings.routeBy + ' ' + '<strong>' + gLoggedInfo.creatorname + '</strong>';
240         }
241
242         this.viewLayer = new OpenLayers.Layer.Vector("View Layer", layerOptions);
243         this.map.addLayers([baseLayer, this.viewLayer]);
244
245         if ($("edit-btn")) {
246             $("edit-btn").observe('click', function() {
247                 $("geom_submit").value = SyjStrings.editAction;
248                 this.messenger.hide();
249                 this.editMode();
250                 this.mode = 'edit';
251             }.bind(this));
252         }
253
254         if ($("create-btn")) {
255             $("create-btn").observe('click', function() {
256                 $("geom_submit").value = SyjStrings.createAction;
257                 this.messenger.hide();
258                 this.editMode();
259                 this.mode = 'create';
260             }.bind(this));
261         }
262
263         if ($("clone-btn")) {
264             $("clone-btn").observe('click', function() {
265                 $("geom_submit").value = SyjStrings.cloneAction;
266                 $("geom_title").value = "";
267                 this.messenger.hide();
268                 this.editMode();
269                 this.mode = 'create';
270                 SyjSaveUI.enableSubmit();
271             }.bind(this));
272         }
273
274         $("geomform").ajaxize({
275                 presubmit: this.prepareForm.bind(this),
276                 onSuccess: this.saveSuccess.bind(this),
277                 onFailure: this.saveFailure.bind(this)
278                 });
279         SyjSaveUI.init().hide();
280
281         this.messenger = $('message');
282         hidemessenger = this.messenger.empty();
283         new CloseBtn(this.messenger, {
284             style: {
285                 margin: "-1em"
286             }
287         });
288         if (hidemessenger) {
289             this.messenger.hide();
290         }
291
292         if (typeof gInitialGeom !== "undefined" && typeof gInitialGeom.data !== "undefined") {
293             this.viewLayer.addFeatures([this.wkt.read(gInitialGeom.data)]);
294             extent = this.viewLayer.getDataExtent();
295             // XXX: ie has not guessed height of map main div yet during map
296             // initialisation. Now, it will read it correctly.
297             this.map.updateSize();
298         } else {
299             extent = new OpenLayers.Bounds(gMaxExtent.minlon, gMaxExtent.minlat, gMaxExtent.maxlon, gMaxExtent.maxlat)
300                                          .transform(WGS84, Mercator);
301         }
302         this.map.zoomToExtent(extent);
303         document.observe('simplebox:shown', this.observer.bindAsEventListener(this));
304     },
305
306     observer: function(evt) {
307         if (evt.eventName === "simplebox:shown" && evt.memo.element !== $("termsofusearea")) {
308             this.messenger.hide();
309         }
310     },
311
312     prepareForm: function(form) {
313         if (!LoginMgr.logged && !$("geom_accept").checked) {
314             this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
315             $("geom_accept_container").highlight('#F08080');
316             $("geom_accept").activate();
317             return false;
318         }
319
320         var line, realPoints, idx;
321
322         line = new OpenLayers.Geometry.LineString();
323         realPoints = this.editControl.handler.realPoints;
324         for (idx = 0; idx < realPoints.length; idx++) {
325             line.addComponent(realPoints[idx].geometry.clone());
326         }
327         this.viewLayer.addFeatures(new OpenLayers.Feature.Vector(line));
328
329         this.viewMode();
330
331         $("geom_data").value = this.wkt.write(new OpenLayers.Feature.Vector(line));
332         if (this.mode === "edit" && typeof gLoggedInfo.pathid !== "undefined") {
333             $("geomform").setAttribute("action", "path/" + gLoggedInfo.pathid.toString() + '/update');
334         } else {
335             $("geomform").setAttribute("action", "path");
336         }
337         this.needsFormResubmit = false;
338         SyjSaveUI.disable.bind(SyjSaveUI).defer();
339         this.messenger.hide();
340         return true;
341     },
342
343     viewMode: function() {
344         var handler = this.editControl.handler;
345         OpenLayers.Handler.ModifiablePath.prototype.finalize.apply(handler, arguments);
346         // we need to recreate them on next createFeature; otherwise
347         // they'll reference destroyed features
348         delete(handler.handlers.drag);
349         delete(handler.handlers.feature);
350         this.editControl.deactivate();
351     },
352
353     editMode: function() {
354         var components, point0, point, pixels, pixel, idx;
355
356         this.initEditControl();
357
358         this.editControl.activate();
359         if (this.viewLayer.features.length) {
360             components = this.viewLayer.features[0].geometry.components;
361             point0 = components[0];
362             if (point0) {
363                 pixel = this.map.getPixelFromLonLat(new OpenLayers.LonLat(point0.x, point0.y));
364                 this.editControl.handler.createFeature(pixel);
365                 this.editControl.handler.lastUp = pixel;
366                 pixels = [];
367                 for (idx = 1; idx < components.length; idx++) {
368                     point = components[idx];
369                     pixels.push(this.map.getPixelFromLonLat(new OpenLayers.LonLat(point.x, point.y)));
370                 }
371                 this.editControl.handler.addPoints(pixels);
372             }
373             this.unsavedRoute = {
374                 features: this.viewLayer.features.invoke('clone'),
375                 title: $("geom_title").value
376             };
377         }
378
379         this.viewLayer.destroyFeatures();
380
381         SyjEditUI.hide();
382         if (this.editControl.handler.realPoints && this.editControl.handler.realPoints.length >= 2) {
383             SyjSaveUI.show();
384             SyjSaveUI.disableSubmit();
385         } else {
386             SyjSaveUI.show().disable();
387         }
388     },
389
390     initEditControl: function() {
391         var styles;
392
393         if (this.editControl) {
394             return;
395         }
396
397         this.editControl = new OpenLayers.Control.DrawFeature(new OpenLayers.Layer.Vector(), OpenLayers.Handler.SyjModifiablePath, {
398             callbacks: {
399                 modify: function(f, line) {
400                     if (!SYJView.unsavedRoute) {
401                         SYJView.unsavedRoute = {};
402                     }
403                     if (this.handler.realPoints.length < 2) {
404                         SyjSaveUI.show().disable();
405                     } else {
406                         SyjSaveUI.show().enable();
407                     }
408                 }
409             },
410
411             handlerOptions: {
412                 layerOptions: {
413                     styleMap: styleMap.edit
414                 }
415             }
416         });
417         this.map.addControl(this.editControl);
418         if (this.editControl.layer.renderer instanceof OpenLayers.Renderer.Canvas) {
419             // using externalGraphic with canvas renderer is definitively too buggy
420             styles = this.editControl.handler.layerOptions.styleMap.styles;
421             styles.select = styles.select_for_canvas;
422         }
423         new CloseBtn($("geomform"), {
424             style : {
425                 marginRight: "-40px",
426                 marginTop: "-20px"
427             },
428             callback: function(form) {
429                 this.viewMode();
430                 this.mode = 'view';
431                 SyjSaveUI.hide();
432                 SyjEditUI.show();
433                 this.messenger.hide();
434
435                 if (this.unsavedRoute && typeof this.unsavedRoute.features !== "undefined") {
436                     this.viewLayer.addFeatures(this.unsavedRoute.features);
437                 }
438                 if (this.unsavedRoute && typeof this.unsavedRoute.title !== "undefined") {
439                     $("geom_title").value = this.unsavedRoute.title;
440                 } else {
441                     $("geom_title").value = "";
442                 }
443                 this.unsavedRoute = null;
444             }.bind(this)
445         });
446     },
447
448     saveSuccess: function(transport) {
449       this.unsavedRoute = null;
450
451       if (transport.responseJSON && (typeof transport.responseJSON.redirect === "string")) {
452           location = transport.responseJSON.redirect;
453           return;
454       }
455
456       this.messenger.setMessage(SyjStrings.saveSuccess, "success");
457       SyjSaveUI.hide();
458       SyjEditUI.show();
459       document.title = $('geom_title').value;
460     },
461
462     saveFailure: function(transport) {
463         var httpCode = 0, message = "";
464
465         if (transport) {
466             httpCode = transport.getStatus();
467         }
468         switch (httpCode) {
469             case 0:
470                 message = SyjStrings.notReachedError;
471             break;
472             case 400:
473             case 404:
474                 message = SyjStrings.requestError;
475                 if (transport.responseJSON) {
476                     switch (transport.responseJSON.message) {
477                         case "uniquepath":
478                             message = SyjStrings.uniquePathError;
479                         break;
480                         default:
481                         break;
482                     }
483                 }
484             break;
485             case 403:
486                 message = "";
487                 SYJLogin.messenger.setMessage(SyjStrings.loginNeeded, "warn");
488                 SYJLogin.modalbox.show();
489                 this.needsFormResubmit = true;
490             break;
491             case 410:
492                 message = SyjStrings.gonePathError;
493             break;
494             case 500:
495                 message = SyjStrings.serverError;
496                 this.needsFormResubmit = true;
497             break;
498             default:
499                 message = SyjStrings.unknownError;
500             break;
501         }
502
503         this.editMode();
504         // is some cases, we let the user resubmit, in some other cases, he
505         // needs to modify the path before submitting again
506         if (this.needsFormResubmit) {
507             SyjSaveUI.enable();
508         }
509
510         this.messenger.setMessage(message, "error");
511     }
512 };
513
514 var SYJModalClass = Class.create({
515     type: "",
516
517     init: function() {
518         this.area = $(this.type + '_area');
519         this.messenger = $(this.type + "_message");
520         this.modalbox = new SimpleBox(this.area, {
521             closeMethods: ["onescapekey", "onouterclick", "onbutton"]
522         });
523
524         $(this.type + "_control_anchor").observe("click", function(evt) {
525             this.modalbox.show();
526             evt.stop();
527         }.bindAsEventListener(this));
528
529         document.observe('simplebox:shown', this.observer.bindAsEventListener(this));
530         document.observe('simplebox:hidden', this.observer.bindAsEventListener(this));
531
532         $(this.type + "form").ajaxize({
533             presubmit: this.presubmit.bind(this),
534             onSuccess: this.success.bind(this),
535             onFailure: this.failure.bind(this)
536         });
537     },
538
539     checkNotEmpty: function(input, message) {
540         if ($(input).value.strip().empty()) {
541             this.messenger.setMessage(message, "warn");
542             $(input).highlight('#F08080').activate();
543             return false;
544         }
545         return true;
546     },
547
548     observer: function(evt) {
549         var simplebox, input;
550
551         if (evt.eventName === "simplebox:shown" && evt.memo.element !== $("termsofusearea")) {
552             simplebox = evt.memo;
553             if (simplebox === this.modalbox) {
554                 input = this.area.select('input[type="text"]')[0];
555                 (function () {
556                     input.activate();
557                 }).defer();
558             } else {
559                 this.modalbox.hide();
560             }
561
562         } else if (evt.eventName === "simplebox:hidden" && evt.memo.element !== $("termsofusearea")) {
563             simplebox = evt.memo;
564             if (simplebox === this.modalbox) {
565                 this.reset();
566             }
567         }
568     },
569
570     failure: function(transport) {
571         var httpCode = 0, message = SyjStrings.unknownError, input; // default message error
572
573         if (transport) {
574             httpCode = transport.getStatus();
575         }
576
577         switch (httpCode) {
578             case 0:
579                 message = SyjStrings.notReachedError;
580             break;
581             case 400:
582             case 404:
583             case 410:
584                 message = SyjStrings.requestError;
585             break;
586             case 500:
587                 message = SyjStrings.serverError;
588             break;
589         }
590
591         this.messenger.setMessage(message, "error");
592         input = this.area.select('input[type="text"]')[0];
593         input.highlight('#F08080').activate();
594     },
595
596     reset: function() {
597         this.messenger.hide();
598         this.area.select('.message').invoke('setMessageStatus', null);
599     }
600 });
601
602 var SYJUserClass = Class.create(SYJModalClass, {
603     type: "user",
604     toubox: null,
605
606     init: function($super) {
607         $super();
608         $("termsofusearea").hide();
609
610         $$("#user_termsofuse_anchor, #geom_termsofuse_anchor").invoke('observe', "click", function(evt) {
611             if (!this.toubox) {
612                 $("termsofusearea").show();
613                 $("termsofuseiframe").setAttribute("src", evt.target.href);
614                 this.toubox = new SimpleBox($("termsofusearea"), {
615                     closeMethods: ["onescapekey", "onouterclick", "onbutton"]
616                 });
617             }
618             this.toubox.show();
619             evt.stop();
620         }.bindAsEventListener(this));
621
622         $$("#login_area_create > a").invoke('observe', 'click',
623             function(evt) {
624                 this.modalbox.show();
625                 evt.stop();
626             }.bindAsEventListener(this));
627
628         $("user_pseudo-desc").hide();
629         $("user_pseudo").observe('contentchange', function(evt) {
630             var value = evt.target.value;
631             PseudoChecker.reset();
632             if (value && !(value.match(/^[a-zA-Z0-9_.]+$/))) {
633                 $("user_pseudo-desc").show().setMessageStatus("warn");
634             } else {
635                 $("user_pseudo-desc").hide();
636             }
637         }).timedobserve(function() {
638             PseudoChecker.check();
639         });
640
641         $("user_password").observe('contentchange', function(evt) {
642             if (evt.target.value.length < 6) {
643                 $("user_password-desc").setMessageStatus("warn");
644             } else {
645                 $("user_password-desc").setMessageStatus("success");
646             }
647         }.bindAsEventListener(this));
648
649         $("account-info").hide();
650         $("account-info-bullet").observe('click', function(evt) {
651             var elt = $("account-info");
652             if (elt.visible()) {
653                 evt.target.src = "icons/bullet_arrow_right.png";
654                 elt.hide();
655             } else {
656                 evt.target.src = "icons/bullet_arrow_down.png";
657                 elt.show();
658             }
659             evt.stop();
660         });
661     },
662
663     presubmit: function() {
664         this.messenger.hide();
665         PseudoChecker.reset();
666         if (!(this.checkNotEmpty("user_pseudo", SyjStrings.userEmptyWarn))) {
667             return false;
668         }
669
670         if (!($("user_pseudo").value.match(/^[a-zA-Z0-9_.]+$/))) {
671             $("user_pseudo-desc").show().setMessageStatus("warn");
672             $("user_pseudo").highlight('#F08080').activate();
673             return false;
674         }
675
676         if (PseudoChecker.exists[$("user_pseudo").value]) {
677             PseudoChecker.availableMessage(false);
678             $("user_pseudo").highlight('#F08080').activate();
679             return false;
680         }
681
682         if (!(this.checkNotEmpty("user_password", SyjStrings.passwordEmptyWarn))) {
683             return false;
684         }
685
686         if ($("user_password").value.length < 6) {
687             $("user_password-desc").setMessageStatus("warn");
688             $("user_password").highlight('#F08080').activate();
689             return false;
690         }
691
692         if ($("user_password").value !== $("user_password_confirm").value) {
693             this.messenger.setMessage(SyjStrings.passwordNoMatchWarn, "warn");
694             $("user_password").highlight('#F08080').activate();
695             return false;
696         }
697
698         if (!(this.checkNotEmpty("user_email", SyjStrings.emailEmptyWarn))) {
699             return false;
700         }
701
702         if (!$("user_accept").checked) {
703             this.messenger.setMessage(SyjStrings.acceptTermsofuseWarn, "warn");
704             $("user_accept_container").highlight('#F08080');
705             $("user_accept").activate();
706             return false;
707         }
708
709         this.reset();
710         return true;
711     },
712
713     success: function(transport) {
714         LoginMgr.login();
715         SYJView.messenger.setMessage(SyjStrings.userSuccess, "success");
716         this.modalbox.hide();
717         if (SYJView.needsFormResubmit) {
718             SYJView.messenger.addMessage(SyjStrings.canResubmit);
719             $("geom_submit").activate();
720         }
721     },
722
723     failure: function($super, transport) {
724         var httpCode = 0, focusInput = null, message = "";
725
726         if (transport) {
727             httpCode = transport.getStatus();
728         }
729
730         focusInput = null;
731         message = "";
732
733         switch (httpCode) {
734             case 400:
735                 if (transport.responseJSON) {
736                     switch (transport.responseJSON.message) {
737                         case "invalidemail":
738                             message = SyjStrings.emailInvalidWarn;
739                             focusInput = $("user_email");
740                         break;
741                         case "uniquepseudo":
742                             PseudoChecker.availableMessage(false);
743                             focusInput = $("user_pseudo");
744                         break;
745                         case "uniqueemail":
746                             message = SyjStrings.uniqueEmailError;
747                             focusInput = $("user_email");
748                         break;
749                     }
750                 }
751             break;
752         }
753
754         if (focusInput) {
755             if (message) {
756                 this.messenger.setMessage(message, "error");
757             }
758             focusInput.highlight('#F08080').activate();
759             return;
760         }
761
762         $super(transport);
763     }
764 });
765 var SYJUser = new SYJUserClass();
766
767 var SYJLoginClass = Class.create(SYJModalClass, {
768     type: "login",
769
770     init: function($super) {
771         $super();
772     },
773
774     presubmit: function() {
775         this.messenger.hide();
776         if (!(this.checkNotEmpty("login_user", SyjStrings.userEmptyWarn))) {
777             return false;
778         }
779
780         this.reset();
781         return true;
782     },
783
784     success: function(transport) {
785         if (transport.responseText === "1") {
786             LoginMgr.login(true);
787         } else {
788             LoginMgr.login();
789         }
790         SYJView.messenger.setMessage(SyjStrings.loginSuccess, "success");
791         this.modalbox.hide();
792         if (SYJView.needsFormResubmit) {
793             SYJView.messenger.addMessage(SyjStrings.canResubmit);
794             $("geom_submit").activate();
795         }
796     },
797
798     failure: function($super, transport) {
799         var httpCode = 0, focusInput = null, message = "";
800
801         if (transport) {
802             httpCode = transport.getStatus();
803         }
804
805         focusInput = null;
806         message = "";
807
808         switch (httpCode) {
809             case 403:
810                 message = SyjStrings.loginFailure;
811                 focusInput = $("login_user");
812             break;
813         }
814
815         if (message) {
816             this.messenger.setMessage(message, "error");
817             if (focusInput) {
818                 focusInput.highlight('#F08080').activate();
819             }
820             return;
821         }
822
823         $super(transport);
824     }
825 });
826 var SYJLogin = new SYJLoginClass();
827
828 var SYJNewpwdClass = Class.create(SYJModalClass, {
829     type: "newpwd",
830
831     presubmit: function() {
832         if (!(this.checkNotEmpty("newpwd_email", SyjStrings.emailEmptyWarn))) {
833             return false;
834         }
835         this.reset();
836         return true;
837     },
838     success: function(transport) {
839         SYJView.messenger.setMessage(SyjStrings.newpwdSuccess, "success");
840         this.modalbox.hide();
841     }
842
843 });
844 var SYJNewpwd = new SYJNewpwdClass();
845
846 var LoginMgr = Object.extend(gLoggedInfo, {
847     controlsdeck: null,
848
849     updateUI: function() {
850         if (!this.controlsdeck) {
851             this.controlsdeck = new Deck("login_controls");
852         }
853         if (this.logged) {
854             this.controlsdeck.setIndex(1);
855             $$(".logged-hide").invoke('hide');
856             $$(".logged-show").invoke('show');
857         } else {
858             this.controlsdeck.setIndex(0);
859             $$(".logged-hide").invoke('show');
860             $$(".logged-show").invoke('hide');
861         }
862
863         if ($("edit-btn")) {
864             if (this.iscreator && SYJView.mode === 'view') {
865                 $("edit-btn").show();
866             } else {
867                 $("edit-btn").hide();
868             }
869         }
870     },
871
872     login: function(aIsCreator) {
873         if (typeof aIsCreator === "boolean") {
874             this.iscreator = aIsCreator;
875         }
876         this.logged = true;
877         this.updateUI();
878     }
879 });
880
881 var PseudoChecker = {
882     req: null,
883     exists: {},
884     currentvalue: null,
885     messageelt: null,
886     throbber: null,
887
888     message: function(str, status, throbber) {
889         var row;
890         if (!this.messageelt) {
891             row = new Element('tr');
892             // we can't use row.update('<td></td><td><div></div></td>')
893             // because gecko would mangle the <td>s
894             row.insert(new Element('td'))
895                .insert((new Element('td')).update(new Element('div')));
896
897             $("user_pseudo").up('tr').insert({after: row});
898             this.messageelt = new Element('span');
899             this.throbber = new Element("img", { src: "icons/pseudo-throbber.gif"});
900             row.down('div').insert(this.throbber).insert(this.messageelt);
901         }
902         if (throbber) {
903             this.throbber.show();
904         } else {
905             this.throbber.hide();
906         }
907         this.messageelt.up().setStyle({visibility: ''});
908         this.messageelt.className = status;
909         this.messageelt.update(str);
910     },
911
912     availableMessage: function(available) {
913         var message = available ? SyjStrings.availablePseudo: SyjStrings.unavailablePseudo,
914             status = available ? "success": "warn";
915         this.message(message, status, false);
916     },
917
918     reset: function() {
919         if (this.req) {
920             this.req.abort();
921             this.req = this.currentvalue = null;
922         }
923         if (this.messageelt) {
924             this.messageelt.up().setStyle({visibility: 'hidden'});
925         }
926     },
927
928     check: function() {
929         var pseudo = $("user_pseudo").value;
930
931         this.reset();
932
933         if (!pseudo || !(pseudo.match(/^[a-zA-Z0-9_.]+$/))) {
934             return;
935         }
936
937         if (typeof this.exists[pseudo] === "boolean") {
938             this.reset();
939             this.availableMessage(!this.exists[pseudo]);
940             return;
941         }
942
943         this.message(SyjStrings.pseudoChecking, "", true);
944
945         this.currentvalue = pseudo;
946         this.req = new Ajax.TimedRequest('userexists/' + encodeURIComponent(pseudo), 20, {
947             onFailure: this.failure.bind(this),
948             onSuccess: this.success.bind(this)
949         });
950     },
951
952     failure: function(transport) {
953         var httpCode = 0, value = this.currentvalue;
954
955         if (transport) {
956             httpCode = transport.getStatus();
957         }
958         this.reset();
959         if (httpCode === 404) {
960             this.exists[value] = false;
961             this.availableMessage(true);
962         }
963
964     },
965
966     success: function(transport) {
967         var httpCode = transport.getStatus(), value = this.currentvalue;
968         this.reset();
969         this.exists[value] = true;
970         this.availableMessage(false);
971     }
972 };
973
974
975 document.observe("dom:loaded", function() {
976     SYJLogin.init();
977     SYJUser.init();
978     SYJView.init();
979     SYJNewpwd.init();
980     LoginMgr.updateUI();
981 });
982 window.onbeforeunload = function() {
983     if (SYJView.unsavedRoute) {
984         return SyjStrings.unsavedConfirmExit;
985     } else {
986         return undefined;
987     }
988 };