[IMP] notify on successful database dump or restoration
[odoo/odoo.git] / addons / web / static / src / js / chrome.js
1 /*---------------------------------------------------------
2  * OpenERP Web chrome
3  *---------------------------------------------------------*/
4 openerp.web.chrome = function(openerp) {
5 var QWeb = openerp.web.qweb,
6     _t = openerp.web._t;
7
8 openerp.web.Notification =  openerp.web.Widget.extend(/** @lends openerp.web.Notification# */{
9     template: 'Notification',
10     identifier_prefix: 'notification-',
11
12     init: function() {
13         this._super.apply(this, arguments);
14         openerp.notification = this;
15     },
16
17     start: function() {
18         this._super.apply(this, arguments);
19         this.$element.notify({
20             speed: 500,
21             expires: 1500
22         });
23     },
24     notify: function(title, text) {
25         this.$element.notify('create', {
26             title: title,
27             text: text
28         });
29     },
30     warn: function(title, text) {
31         this.$element.notify('create', 'oe_notification_alert', {
32             title: title,
33             text: text
34         });
35     }
36
37 });
38
39 openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{
40     dialog_title: "",
41     identifier_prefix: 'dialog',
42     /**
43      * @constructs openerp.web.Dialog
44      * @extends openerp.web.OldWidget
45      *
46      * @param parent
47      * @param options
48      */
49     init: function (parent, options) {
50         var self = this;
51         this._super(parent);
52         this.dialog_options = {
53             modal: true,
54             destroy_on_close: true,
55             width: $(window).width() * (($(window).width() > 1024) ? 0.5 : 0.75),
56             min_width: 0,
57             max_width: '95%',
58             height: 'auto',
59             min_height: 0,
60             max_height: this.get_height('100%') - 140,
61             autoOpen: false,
62             position: [false, 50],
63             buttons: {},
64             beforeClose: function () { self.on_close(); },
65             resizeStop: this.on_resized
66         };
67         for (var f in this) {
68             if (f.substr(0, 10) == 'on_button_') {
69                 this.dialog_options.buttons[f.substr(10)] = this[f];
70             }
71         }
72         if (options) {
73             _.extend(this.dialog_options, options);
74         }
75     },
76     get_options: function(options) {
77         var self = this,
78             o = _.extend({}, this.dialog_options, options || {});
79         _.each(['width', 'height'], function(unit) {
80             o[unit] = self['get_' + unit](o[unit]);
81             o['min_' + unit] = self['get_' + unit](o['min_' + unit] || 0);
82             o['max_' + unit] = self['get_' + unit](o['max_' + unit] || 0);
83             if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) o[unit] = o['min_' + unit];
84             if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) o[unit] = o['max_' + unit];
85         });
86         if (!o.title && this.dialog_title) {
87             o.title = this.dialog_title;
88         }
89         return o;
90     },
91     get_width: function(val) {
92         return this.get_size(val.toString(), $(window.top).width());
93     },
94     get_height: function(val) {
95         return this.get_size(val.toString(), $(window.top).height());
96     },
97     get_size: function(val, available_size) {
98         if (val === 'auto') {
99             return val;
100         } else if (val.slice(-1) == "%") {
101             return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
102         } else {
103             return parseInt(val, 10);
104         }
105     },
106     start: function () {
107         this.$element.dialog(this.dialog_options);
108         this._super();
109         return this;
110     },
111     open: function(options) {
112         // TODO fme: bind window on resize
113         if (this.template) {
114             this.$element.html(this.render());
115         }
116         var o = this.get_options(options);
117         this.$element.dialog(o).dialog('open');
118         if (o.height === 'auto' && o.max_height) {
119             this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' });
120         }
121         return this;
122     },
123     close: function() {
124         this.$element.dialog('close');
125     },
126     on_close: function() {
127         if (this.dialog_options.destroy_on_close) {
128             this.$element.dialog('destroy');
129         }
130     },
131     on_resized: function() {
132         openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height());
133     },
134     stop: function () {
135         // Destroy widget
136         this.close();
137         this.$element.dialog('destroy');
138         this._super();
139     }
140 });
141
142 openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
143     init: function() {
144         this._super();
145         openerp.connection.on_rpc_error.add(this.on_rpc_error);
146     },
147     on_rpc_error: function(error) {
148         this.error = error;
149         if (error.data.fault_code) {
150             var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
151             if (split.length > 1) {
152                 error.type = split.shift();
153                 error.data.fault_code = error.data.fault_code.substr(error.type.length + 4);
154             }
155         }
156         if (error.code === 200 && error.type) {
157             this.on_managed_error(error);
158         } else {
159             this.on_traceback(error);
160         }
161     },
162     on_managed_error: function(error) {
163         $('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>').dialog({
164             title: "OpenERP " + _.str.capitalize(error.type),
165             buttons: [
166                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
167             ]
168         });
169     },
170     on_traceback: function(error) {
171         var self = this;
172         var buttons = {};
173         if (openerp.connection.openerp_entreprise) {
174             buttons[_t("Send OpenERP Enterprise Report")] = function() {
175                 $this = $(this);
176                 var issuename = $('#issuename').val();
177                 var explanation = $('#explanation').val();
178                 var remark = $('#remark').val();
179                 // Call the send method from server to send mail with details
180                 new openerp.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){
181                     if (result === false) {
182                         alert('There was a communication error.')
183                     } else {
184                         $this.dialog('close');
185                     }
186                 });
187             };
188             buttons[_t("Dont send")] = function() {
189                 $(this).dialog("close");
190             };
191         } else {
192             buttons[_t("Ok")] = function() {
193                 $(this).dialog("close");
194             };
195         }
196         var dialog = new openerp.web.Dialog(this, {
197             title: "OpenERP " + _.str.capitalize(this.error.type),
198             autoOpen: true,
199             width: '80%',
200             height: '50%',
201             min_width: '800px',
202             min_height: '600px',
203             buttons: buttons
204         }).start();
205         dialog.$element.html(QWeb.render('CrashManagerError', {session: openerp.connection, error: error}));
206     },
207 });
208
209 openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# */{
210     template: 'Loading',
211     /**
212      * @constructs openerp.web.Loading
213      * @extends openerp.web.Widget
214      *
215      * @param parent
216      * @param element_id
217      */
218     init: function(parent) {
219         this._super(parent);
220         this.count = 0;
221         this.blocked_ui = false;
222         var self = this;
223         this.request_call = function() {
224             self.on_rpc_event(1);
225         };
226         this.response_call = function() {
227             self.on_rpc_event(-1);
228         };
229         this.session.on_rpc_request.add_first(this.request_call);
230         this.session.on_rpc_response.add_last(this.response_call);
231     },
232     stop: function() {
233         this.session.on_rpc_request.remove(this.request_call);
234         this.session.on_rpc_response.remove(this.response_call);
235         this.on_rpc_event(-this.count);
236         this._super();
237     },
238     on_rpc_event : function(increment) {
239         var self = this;
240         if (!this.count && increment === 1) {
241             // Block UI after 3s
242             this.long_running_timer = setTimeout(function () {
243                 self.blocked_ui = true;
244                 $.blockUI();
245             }, 3000);
246         }
247
248         this.count += increment;
249         if (this.count > 0) {
250             //this.$element.html(QWeb.render("Loading", {}));
251             $(".loading",this.$element).html("Loading ("+this.count+")");
252             $(".loading",this.$element).show();
253             this.widget_parent.$element.addClass('loading');
254         } else {
255             this.count = 0;
256             clearTimeout(this.long_running_timer);
257             // Don't unblock if blocked by somebody else
258             if (self.blocked_ui) {
259                 this.blocked_ui = false;
260                 $.unblockUI();
261             }
262             $(".loading",this.$element).fadeOut();
263             this.widget_parent.$element.removeClass('loading');
264         }
265     }
266 });
267
268 openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database# */{
269     template: "DatabaseManager",
270     /**
271      * @constructs openerp.web.Database
272      * @extends openerp.web.Widget
273      *
274      * @param parent
275      * @param element_id
276      * @param option_id
277      */
278     init: function(parent, element_id, option_id) {
279         this._super(parent, element_id);
280         this.unblockUIFunction = $.unblockUI;
281     },
282     start: function() {
283         this.$option_id = $("#oe_db_options");
284
285         var self = this;
286         var fetch_db = this.rpc("/web/database/get_list", {}, function(result) {
287             self.db_list = result.db_list;
288         });
289         var fetch_langs = this.rpc("/web/session/get_lang_list", {}, function(result) {
290             if (result.error) {
291                 self.display_error(result);
292                 return;
293             }
294             self.lang_list = result.lang_list;
295         });
296         $.when(fetch_db, fetch_langs).then(function () {self.do_create();});
297
298         this.$element.find('#db-create').click(this.do_create);
299         this.$element.find('#db-drop').click(this.do_drop);
300         this.$element.find('#db-backup').click(this.do_backup);
301         this.$element.find('#db-restore').click(this.do_restore);
302         this.$element.find('#db-change-password').click(this.do_change_password);
303         this.$element.find('#back-to-login').click(function() {
304             self.hide();
305         });
306     },
307     stop: function () {
308         this.hide();
309         this.$option_id.empty();
310
311         this.$element
312             .find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login')
313                 .unbind('click')
314             .end()
315             .empty();
316         this._super();
317     },
318     show: function () {
319         this.$element.closest(".login")
320                 .addClass("database_block");
321     },
322     hide: function () {
323         this.$element.closest(".login")
324                 .removeClass("database_block")
325     },
326     /**
327      * Converts a .serializeArray() result into a dict. Does not bother folding
328      * multiple identical keys into an array, last key wins.
329      *
330      * @param {Array} array
331      */
332     to_object: function (array) {
333         var result = {};
334         _(array).each(function (record) {
335             result[record.name] = record.value;
336         });
337         return result;
338     },
339     /**
340      * Waits until the new database is done creating, then unblocks the UI and
341      * logs the user in as admin
342      *
343      * @param {Number} db_creation_id identifier for the db-creation operation, used to fetch the current installation progress
344      * @param {Object} info info fields for this database creation
345      * @param {String} info.db name of the database being created
346      * @param {String} info.password super-admin password for the database
347      */
348     wait_for_newdb: function (db_creation_id, info) {
349         var self = this;
350         self.rpc('/web/database/progress', {
351             id: db_creation_id,
352             password: info.password
353         }, function (result) {
354             var progress = result[0];
355             // I'd display a progress bar, but turns out the progress status
356             // the server report kind-of blows goats: it's at 0 for ~75% of
357             // the installation, then jumps to 75%, then jumps down to either
358             // 0 or ~40%, then back up to 75%, then terminates. Let's keep that
359             // mess hidden behind a not-very-useful but not overly weird
360             // message instead.
361             if (progress < 1) {
362                 setTimeout(function () {
363                     self.wait_for_newdb(db_creation_id, info);
364                 }, 500);
365                 return;
366             }
367
368             var admin = result[1][0];
369             setTimeout(function () {
370                 self.widget_parent.do_login(
371                         info.db, admin.login, admin.password);
372                 self.stop();
373                 self.unblockUI();
374             });
375         });
376     },
377     /**
378      * Blocks UI and replaces $.unblockUI by a noop to prevent third parties
379      * from unblocking the UI
380      */
381     blockUI: function () {
382         $.blockUI();
383         $.unblockUI = function () {};
384     },
385     /**
386      * Reinstates $.unblockUI so third parties can play with blockUI, and
387      * unblocks the UI
388      */
389     unblockUI: function () {
390         $.unblockUI = this.unblockUIFunction;
391         $.unblockUI();
392     },
393     /**
394      * Displays an error dialog resulting from the various RPC communications
395      * failing over themselves
396      *
397      * @param {Object} error error description
398      * @param {String} error.title title of the error dialog
399      * @param {String} error.error message of the error dialog
400      */
401     display_error: function (error) {
402         return $('<div>').dialog({
403             modal: true,
404             title: error.title,
405             buttons: [
406                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
407             ]
408         }).html(error.error);
409     },
410     do_create: function() {
411         var self = this;
412         self.$option_id.html(QWeb.render("Database.CreateDB", self));
413         self.$option_id.find("form[name=create_db_form]").validate({
414             submitHandler: function (form) {
415                 var fields = $(form).serializeArray();
416                 self.blockUI();
417                 self.rpc("/web/database/create", {'fields': fields}, function(result) {
418                     if (result.error) {
419                         self.unblockUI();
420                         self.display_error(result);
421                         return;
422                     }
423                     self.db_list.push(self.to_object(fields)['db_name']);
424                     self.db_list.sort();
425                     self.widget_parent.set_db_list(self.db_list);
426                     var form_obj = self.to_object(fields);
427                     self.wait_for_newdb(result, {
428                         password: form_obj['super_admin_pwd'],
429                         db: form_obj['db_name']
430                     });
431                 });
432             }
433         });
434     },
435     do_drop: function() {
436         var self = this;
437         self.$option_id.html(QWeb.render("DropDB", self));
438         self.$option_id.find("form[name=drop_db_form]").validate({
439             submitHandler: function (form) {
440                 var $form = $(form),
441                     fields = $form.serializeArray(),
442                     $db_list = $form.find('select[name=drop_db]'),
443                     db = $db_list.val();
444
445                 if (!confirm("Do you really want to delete the database: " + db + " ?")) {
446                     return;
447                 }
448                 self.rpc("/web/database/drop", {'fields': fields}, function(result) {
449                     if (result.error) {
450                         self.display_error(result);
451                         return;
452                     }
453                     $db_list.find(':selected').remove();
454                     self.db_list.splice(_.indexOf(self.db_list, db, true), 1);
455                     self.widget_parent.set_db_list(self.db_list);
456                     self.do_notify("Dropping database", "The database '" + db + "' has been dropped");
457                 });
458             }
459         });
460     },
461     do_backup: function() {
462         var self = this;
463         self.$option_id
464             .html(QWeb.render("BackupDB", self))
465             .find("form[name=backup_db_form]").validate({
466             submitHandler: function (form) {
467                 self.blockUI();
468                 self.session.get_file({
469                     form: form,
470                     error: function (body) {
471                         var error = body.firstChild.data.split('|');
472                         self.display_error({
473                             title: error[0],
474                             error: error[1]
475                         });
476                     },
477                     complete: function() {
478                         self.unblockUI();
479                         self.do_notify(_t("Backed"), _t("Database backed up successfully"));
480                     }
481                 });
482             }
483         });
484     },
485     do_restore: function() {
486         var self = this;
487         self.$option_id.html(QWeb.render("RestoreDB", self));
488
489         self.$option_id.find("form[name=restore_db_form]").validate({
490             submitHandler: function (form) {
491                 self.blockUI();
492                 $(form).ajaxSubmit({
493                     url: '/web/database/restore',
494                     type: 'POST',
495                     resetForm: true,
496                     success: function (body) {
497                         // TODO: ui manipulations
498                         // note: response objects don't work, but we have the
499                         // HTTP body of the response~~
500
501                         // If empty body, everything went fine
502                         if (!body) { return; }
503
504                         if (body.indexOf('403 Forbidden') !== -1) {
505                             self.display_error({
506                                 title: 'Access Denied',
507                                 error: 'Incorrect super-administrator password'
508                             })
509                         } else {
510                             self.display_error({
511                                 title: 'Restore Database',
512                                 error: 'Could not restore the database'
513                             })
514                         }
515                     },
516                     complete: function() {
517                         self.unblockUI();
518                         self.do_notify(_t("Restored"), _t("Database restored successfully"));
519                     }
520                 });
521             }
522         });
523     },
524     do_change_password: function() {
525         var self = this;
526         self.$option_id.html(QWeb.render("Change_DB_Pwd", self));
527
528         self.$option_id.find("form[name=change_pwd_form]").validate({
529             messages: {
530                 old_pwd: "Please enter your previous password",
531                 new_pwd: "Please enter your new password",
532                 confirm_pwd: {
533                     required: "Please confirm your new password",
534                     equalTo: "The confirmation does not match the password"
535                 }
536             },
537             submitHandler: function (form) {
538                 self.rpc("/web/database/change_password", {
539                     'fields': $(form).serializeArray()
540                 }, function(result) {
541                     if (result.error) {
542                         self.display_error(result);
543                         return;
544                     }
545                     self.do_notify("Changed Password", "Password has been changed successfully");
546                 });
547             }
548         });
549     }
550 });
551
552 openerp.web.Login =  openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
553     remember_credentials: true,
554     
555     template: "Login",
556     identifier_prefix: 'oe-app-login-',
557     /**
558      * @constructs openerp.web.Login
559      * @extends openerp.web.Widget
560      *
561      * @param parent
562      * @param element_id
563      */
564
565     init: function(parent) {
566         this._super(parent);
567         this.has_local_storage = typeof(localStorage) != 'undefined';
568         this.selected_db = null;
569         this.selected_login = null;
570
571         if (this.has_local_storage && this.remember_credentials) {
572             this.selected_db = localStorage.getItem('last_db_login_success');
573             this.selected_login = localStorage.getItem('last_login_login_success');
574             if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) {
575                 this.selected_password = localStorage.getItem('last_password_login_success');
576             }
577         }
578     },
579     start: function() {
580         var self = this;
581         this.database = new openerp.web.Database(this);
582         this.database.appendTo(this.$element);
583
584         this.$element.find('#oe-db-config').click(function() {
585             self.database.show();
586         });
587
588         this.$element.find("form").submit(this.on_submit);
589
590         this.rpc("/web/database/get_list", {}, function(result) {
591             self.set_db_list(result.db_list);
592         }, 
593         function(error, event) {
594             if (error.data.fault_code === 'AccessDenied') {
595                 event.preventDefault();
596             }
597         });
598
599     },
600     set_db_list: function (list) {
601         this.$element.find("[name=db]").replaceWith(
602             openerp.web.qweb.render('Login_dblist', {
603                 db_list: list, selected_db: this.selected_db}))
604     },
605     on_submit: function(ev) {
606         if(ev) {
607             ev.preventDefault();
608         }
609         var $e = this.$element;
610         var db = $e.find("form [name=db]").val();
611         var login = $e.find("form input[name=login]").val();
612         var password = $e.find("form input[name=password]").val();
613
614         this.do_login(db, login, password);
615     },
616     /**
617      * Performs actual login operation, and UI-related stuff
618      *
619      * @param {String} db database to log in
620      * @param {String} login user login
621      * @param {String} password user password
622      */
623     do_login: function (db, login, password) {
624         var self = this;
625         this.session.on_session_invalid.add({
626             callback: function () {
627                 self.$element.addClass("login_invalid");
628             },
629             unique: true
630         });
631         this.session.session_authenticate(db, login, password).then(function() {
632             self.$element.removeClass("login_invalid");
633             if (self.has_local_storage) {
634                 if(self.remember_credentials) {
635                     localStorage.setItem('last_db_login_success', db);
636                     localStorage.setItem('last_login_login_success', login);
637                     if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) {
638                         localStorage.setItem('last_password_login_success', password);
639                     }
640                 } else {
641                     localStorage.setItem('last_db_login_success', '');
642                     localStorage.setItem('last_login_login_success', '');
643                     localStorage.setItem('last_password_login_success', '');
644                 }
645             }
646         });
647     },
648 });
649
650 openerp.web.Header =  openerp.web.Widget.extend(/** @lends openerp.web.Header# */{
651     template: "Header",
652     identifier_prefix: 'oe-app-header-',
653     /**
654      * @constructs openerp.web.Header
655      * @extends openerp.web.Widget
656      *
657      * @param parent
658      */
659     init: function(parent) {
660         this._super(parent);
661         this.qs = "?" + jQuery.param.querystring();
662         this.$content = $();
663         this.update_promise = $.Deferred().resolve();
664     },
665     start: function() {
666         this._super();
667     },
668     do_update: function () {
669         var self = this;
670         var fct = function() {
671             self.$content.remove();
672             if (!self.session.uid)
673                 return;
674             var func = new openerp.web.Model("res.users").get_func("read");
675             return func(self.session.uid, ["name", "company_id"]).pipe(function(res) {
676                 self.$content = $(QWeb.render("Header-content", {widget: self, user: res}));
677                 self.$content.appendTo(self.$element);
678                 self.$element.find(".logout").click(self.on_logout);
679                 self.$element.find("a.preferences").click(self.on_preferences);
680                 self.$element.find(".about").click(self.on_about);
681                 return self.shortcut_load();
682             });
683         };
684         this.update_promise = this.update_promise.pipe(fct, fct);
685     },
686     on_about: function() {
687         var self = this;
688         self.rpc("/web/webclient/version_info", {}).then(function(res) {
689             var $help = $(QWeb.render("About-Page", {version_info: res}));
690             $help.dialog({autoOpen: true,
691                 modal: true, width: 960, title: _t("About")});
692         });
693     },
694     shortcut_load :function(){
695         var self = this,
696             sc = self.session.shortcuts,
697             shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc');
698         // TODO: better way to communicate between sections.
699         // sc.bindings, because jquery does not bind/trigger on arrays...
700         if (!sc.binding) {
701             sc.binding = {};
702             $(sc.binding).bind({
703                 'add': function (e, attrs) {
704                     shortcuts_ds.create(attrs, function (out) {
705                         $('<li>', {
706                             'data-shortcut-id':out.result,
707                             'data-id': attrs.res_id
708                         }).text(attrs.name)
709                           .appendTo(self.$element.find('.oe-shortcuts ul'));
710                         attrs.id = out.result;
711                         sc.push(attrs);
712                     });
713                 },
714                 'remove-current': function () {
715                     var menu_id = self.session.active_id;
716                     var $shortcut = self.$element
717                         .find('.oe-shortcuts li[data-id=' + menu_id + ']');
718                     var shortcut_id = $shortcut.data('shortcut-id');
719                     $shortcut.remove();
720                     shortcuts_ds.unlink([shortcut_id]);
721                     var sc_new = _.reject(sc, function(shortcut){ return shortcut_id === shortcut.id});
722                     sc.splice(0, sc.length);
723                     sc.push.apply(sc, sc_new);
724                     }
725             });
726         }
727         return this.rpc('/web/session/sc_list', {}, function(shortcuts) {
728             sc.splice(0, sc.length);
729             sc.push.apply(sc, shortcuts);
730
731             self.$element.find('.oe-shortcuts')
732                 .html(QWeb.render('Shortcuts', {'shortcuts': shortcuts}))
733                 .undelegate('li', 'click')
734
735                 .delegate('li', 'click', function(e) {
736                     e.stopPropagation();
737                     var id = $(this).data('id');
738                     self.session.active_id = id;
739                     self.rpc('/web/menu/action', {'menu_id':id}, function(ir_menu_data) {
740                         if (ir_menu_data.action.length){
741                             self.on_action(ir_menu_data.action[0][2]);
742                         }
743                     });
744                 });
745         });
746     },
747
748     on_action: function(action) {
749     },
750     on_preferences: function(){
751         var self = this;
752         var action_manager = new openerp.web.ActionManager(this);
753         var dataset = new openerp.web.DataSet (this,'res.users',this.context);
754         dataset.call ('action_get','',function (result){
755             self.rpc('/web/action/load', {action_id:result}, function(result){
756                 action_manager.do_action(_.extend(result['result'], {
757                     res_id: self.session.uid,
758                     res_model: 'res.users',
759                     flags: {
760                         action_buttons: false,
761                         search_view: false,
762                         sidebar: false,
763                         views_switcher: false,
764                         pager: false
765                     }
766                 }));
767             });
768         });
769         this.dialog = new openerp.web.Dialog(this,{
770             title: _t("Preferences"),
771             width: '700px',
772             buttons: [
773                 {text: _t("Change password"), click: function(){ self.change_password(); }},
774                 {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }},
775                 {text: _t("Save"), click: function(){
776                         var inner_viewmanager = action_manager.inner_viewmanager;
777                         inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save()
778                         .then(function() {
779                             self.dialog.stop();
780                             window.location.reload();
781                         });
782                     }
783                 }
784             ]
785         });
786        this.dialog.start().open();
787        action_manager.appendTo(this.dialog);
788        action_manager.render(this.dialog);
789     },
790
791     change_password :function() {
792         var self = this;
793         this.dialog = new openerp.web.Dialog(this, {
794             title: _t("Change Password"),
795             width : 'auto'
796         });
797         this.dialog.start().open();
798         this.dialog.$element.html(QWeb.render("Change_Pwd", self));
799         this.dialog.$element.find("form[name=change_password_form]").validate({
800             submitHandler: function (form) {
801                 self.rpc("/web/session/change_password",{
802                     'fields': $(form).serializeArray()
803                 }, function(result) {
804                     if (result.error) {
805                         self.display_error(result);
806                         return;
807                     } else {
808                         openerp.webclient.on_logout();
809                     }
810                 });
811             }
812         });
813     },
814     display_error: function (error) {
815         return $('<div>').dialog({
816             modal: true,
817             title: error.title,
818             buttons: [
819                 {text: _("Ok"), click: function() { $(this).dialog("close"); }}
820             ]
821         }).html(error.error);
822     },
823     on_logout: function() {
824     }
825 });
826
827 openerp.web.Menu =  openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
828     /**
829      * @constructs openerp.web.Menu
830      * @extends openerp.web.Widget
831      *
832      * @param parent
833      * @param element_id
834      * @param secondary_menu_id
835      */
836     init: function(parent, element_id, secondary_menu_id) {
837         this._super(parent, element_id);
838         this.secondary_menu_id = secondary_menu_id;
839         this.$secondary_menu = $("#" + secondary_menu_id);
840         this.menu = false;
841         this.folded = false;
842         if (window.localStorage) {
843             this.folded = localStorage.getItem('oe_menu_folded') === 'true';
844         }
845         this.float_timeout = 700;
846     },
847     start: function() {
848         this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded');
849     },
850     do_reload: function() {
851         var self = this;
852         return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () {
853             if (self.current_menu) {
854                 self.open_menu(self.current_menu);
855             }
856         });
857     },
858     on_loaded: function(data) {
859         this.data = data;
860         this.$element.html(QWeb.render("Menu", { widget : this }));
861         this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this }));
862         this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click);
863         this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold);
864     },
865     on_toggle_fold: function() {
866         this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded');
867         if (this.folded) {
868             this.$secondary_menu.find('.oe_secondary_menu.active').show();
869         } else {
870             this.$secondary_menu.find('.oe_secondary_menu').hide();
871         }
872         this.folded = !this.folded;
873         if (window.localStorage) {
874             localStorage.setItem('oe_menu_folded', this.folded.toString());
875         }
876     },
877     /**
878      * Opens a given menu by id, as if a user had browsed to that menu by hand
879      * except does not trigger any event on the way
880      *
881      * @param {Number} menu_id database id of the terminal menu to select
882      */
883     open_menu: function (menu_id) {
884         this.$element.add(this.$secondary_menu).find('.active')
885                 .removeClass('active');
886         this.$secondary_menu.find('> .oe_secondary_menu').hide();
887
888         var $primary_menu;
889         var $secondary_submenu = this.$secondary_menu.find(
890                 'a[data-menu=' + menu_id +']');
891         if ($secondary_submenu.length) {
892             for(;;) {
893                 if ($secondary_submenu.hasClass('leaf')) {
894                     $secondary_submenu.addClass('active');
895                 } else if ($secondary_submenu.hasClass('submenu')) {
896                     $secondary_submenu.addClass('opened')
897                 }
898                 var $parent = $secondary_submenu.parent().show();
899                 if ($parent.hasClass('oe_secondary_menu')) {
900                     var primary_id = $parent.data('menu-parent');
901                     $primary_menu = this.$element.find(
902                             'a[data-menu=' + primary_id + ']');
903                     break;
904                 }
905                 $secondary_submenu = $parent.prev();
906             }
907         } else {
908             $primary_menu = this.$element.find('a[data-menu=' + menu_id + ']');
909         }
910         if (!$primary_menu.length) {
911             return;
912         }
913         $primary_menu.addClass('active');
914         this.$secondary_menu.find(
915             'div[data-menu-parent=' + $primary_menu.data('menu') + ']').show();
916     },
917     on_menu_click: function(ev, id) {
918         id = id || 0;
919         var $clicked_menu, manual = false;
920
921         if (id) {
922             // We can manually activate a menu with it's id (for hash url mapping)
923             manual = true;
924             $clicked_menu = this.$element.find('a[data-menu=' + id + ']');
925             if (!$clicked_menu.length) {
926                 $clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
927             }
928         } else {
929             $clicked_menu = $(ev.currentTarget);
930             id = $clicked_menu.data('menu');
931         }
932
933         if (this.do_menu_click($clicked_menu, manual) && id) {
934             this.current_menu = id;
935             this.session.active_id = id;
936             this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded);
937         }
938         if (ev) {
939             ev.stopPropagation();
940         }
941         return false;
942     },
943     do_menu_click: function($clicked_menu, manual) {
944         var $sub_menu, $main_menu,
945             active = $clicked_menu.is('.active'),
946             sub_menu_visible = false;
947
948         if (this.$secondary_menu.has($clicked_menu).length) {
949             $sub_menu = $clicked_menu.parents('.oe_secondary_menu');
950             $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
951         } else {
952             $sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
953             $main_menu = $clicked_menu;
954         }
955
956         sub_menu_visible = $sub_menu.is(':visible');
957         this.$secondary_menu.find('.oe_secondary_menu').hide();
958
959         $('.active', this.$element.add(this.$secondary_menu)).removeClass('active');
960         $main_menu.add($clicked_menu).add($sub_menu).addClass('active');
961
962         if (!(this.folded && manual)) {
963             this.do_show_secondary($sub_menu, $main_menu);
964         } else {
965             this.do_show_secondary();
966         }
967
968         if ($main_menu != $clicked_menu) {
969             if ($clicked_menu.is('.submenu')) {
970                 $sub_menu.find('.submenu.opened').each(function() {
971                     if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) {
972                         $(this).removeClass('opened').next().hide();
973                     }
974                 });
975                 $clicked_menu.toggleClass('opened').next().toggle();
976             } else if ($clicked_menu.is('.leaf')) {
977                 $sub_menu.toggle(!this.folded);
978                 return true;
979             }
980         } else if (this.folded) {
981             if (active && sub_menu_visible) {
982                 $sub_menu.hide();
983                 return true;
984             }
985             return manual;
986         } else {
987             return true;
988         }
989         return false;
990     },
991     do_hide_secondary: function() {
992         this.$secondary_menu.hide();
993     },
994     do_show_secondary: function($sub_menu, $main_menu) {
995         var self = this;
996         this.$secondary_menu.show();
997         if (!arguments.length) {
998             return;
999         }
1000         if (this.folded) {
1001             var css = $main_menu.position(),
1002                 fold_width = this.$secondary_menu.width() + 2,
1003                 window_width = $(window).width();
1004             css.top += 33;
1005             css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2);
1006             css.left = css.left < fold_width ? fold_width : css.left;
1007             if ((css.left + $sub_menu.width()) > window_width) {
1008                 delete(css.left);
1009                 css.right = 1;
1010             }
1011             $sub_menu.css(css);
1012             $sub_menu.mouseenter(function() {
1013                 clearTimeout($sub_menu.data('timeoutId'));
1014                 $sub_menu.data('timeoutId', null);
1015                 return false;
1016             }).mouseleave(function(evt) {
1017                 var timeoutId = setTimeout(function() {
1018                     if (self.folded && $sub_menu.data('timeoutId')) {
1019                         $sub_menu.hide().unbind('mouseenter').unbind('mouseleave');
1020                     }
1021                 }, self.float_timeout);
1022                 $sub_menu.data('timeoutId', timeoutId);
1023                 return false;
1024             });
1025         }
1026         $sub_menu.show();
1027     },
1028     on_menu_action_loaded: function(data) {
1029         var self = this;
1030         if (data.action.length) {
1031             var action = data.action[0][2];
1032             action.from_menu = true;
1033             self.on_action(action);
1034         } else {
1035             self.on_action({type: 'null_action'});
1036         }
1037     },
1038     on_action: function(action) {
1039     }
1040 });
1041
1042 openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{
1043     /**
1044      * @constructs openerp.web.WebClient
1045      * @extends openerp.web.Widget
1046      *
1047      * @param element_id
1048      */
1049     init: function(parent) {
1050         var self = this;
1051         this._super(parent);
1052         openerp.webclient = this;
1053
1054         this._current_state = null;
1055     },
1056     render_element: function() {
1057         this.$element = $('<body/>');
1058         this.$element.attr("id", "oe");
1059         this.$element.addClass("openerp");
1060     },
1061     start: function() {
1062         var self = this;
1063         if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
1064             this.$element.addClass("kitten-mode-activated");
1065             this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) {
1066                 self.$element.toggleClass('clark-gable');
1067             });
1068         }
1069         this.session.bind().then(function() {
1070             if (!self.session.session_is_valid()) {
1071                 self.show_login();
1072             }
1073         });
1074         this.session.on_session_valid.add(function() {
1075             self.show_application();
1076             
1077             self.header.do_update();
1078             self.menu.do_reload();
1079             if(self.action_manager)
1080                 self.action_manager.stop();
1081             self.action_manager = new openerp.web.ActionManager(self);
1082             self.action_manager.appendTo($("#oe_app"));
1083             self.bind_hashchange();
1084             if (!self.session.openerp_entreprise) {
1085                 self.$element.find('.oe_footer_powered').append('<span> - <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">Unsupported/Community Version</a></span>');
1086                 $('title').html('OpenERP - Unsupported/Community Version');
1087             }
1088         });
1089     },
1090     show_login: function() {
1091         var self = this;
1092         this.destroy_content();
1093         this.show_common();
1094         self.login = new openerp.web.Login(self);
1095         self.login.appendTo(self.$element);
1096     },
1097     show_application: function() {
1098         var self = this;
1099         this.destroy_content();
1100         this.show_common();
1101         self.$table = $(QWeb.render("Interface", {}));
1102         self.$element.append(self.$table);
1103         self.header = new openerp.web.Header(self);
1104         self.header.on_logout.add(self.on_logout);
1105         self.header.on_action.add(self.on_menu_action);
1106         self.header.appendTo($("#oe_header"));
1107         self.menu = new openerp.web.Menu(self, "oe_menu", "oe_secondary_menu");
1108         self.menu.on_action.add(self.on_menu_action);
1109         self.menu.start();
1110     },
1111     show_common: function() {
1112         var self = this;
1113         self.crashmanager =  new openerp.web.CrashManager();
1114         self.notification = new openerp.web.Notification(self);
1115         self.notification.appendTo(self.$element);
1116         self.loading = new openerp.web.Loading(self);
1117         self.loading.appendTo(self.$element);
1118     },
1119     destroy_content: function() {
1120         _.each(_.clone(this.widget_children), function(el) {
1121             el.stop();
1122         });
1123         this.$element.children().remove();
1124     },
1125     do_reload: function() {
1126         return this.session.session_init().pipe(_.bind(function() {this.menu.do_reload();}, this));
1127     },
1128     do_notify: function() {
1129         var n = this.notification;
1130         n.notify.apply(n, arguments);
1131     },
1132     do_warn: function() {
1133         var n = this.notification;
1134         n.warn.apply(n, arguments);
1135     },
1136     on_logout: function() {
1137         this.session.session_logout();
1138         $(window).unbind('hashchange', this.on_hashchange);
1139         this.do_push_state({});
1140         //would be cool to be able to do this, but I think it will make addons do strange things
1141         //this.show_login();
1142         window.location.reload();
1143     },
1144     bind_hashchange: function() {
1145         $(window).bind('hashchange', this.on_hashchange);
1146
1147         var state = $.bbq.getState(true);
1148         if (! _.isEmpty(state)) {
1149             $(window).trigger('hashchange');
1150         } else {
1151             this.action_manager.do_action({type: 'ir.actions.client', tag: 'default_home'});
1152         }
1153     },
1154     on_hashchange: function(event) {
1155         var state = event.getState(true);
1156         if (!_.isEqual(this._current_state, state)) {
1157             this.action_manager.do_load_state(state);
1158         }
1159         this._current_state = state;
1160     },
1161     do_push_state: function(state) {
1162         var url = '#' + $.param(state);
1163         this._current_state = _.clone(state);
1164         $.bbq.pushState(url);
1165     },
1166     on_menu_action: function(action) {
1167         this.action_manager.do_action(action);
1168     },
1169     do_action: function(action) {
1170         var self = this;
1171         // TODO replace by client action menuclick 
1172         if(action.menu_id) {
1173             this.do_reload().then(function () {
1174                 self.menu.on_menu_click(null, action.menu_id);
1175             });
1176         }
1177     },
1178 });
1179
1180 openerp.web.EmbeddedClient = openerp.web.Widget.extend({
1181     template: 'EmptyComponent',
1182     init: function(action_id, options) {
1183         this._super();
1184         // TODO take the xmlid of a action instead of its id 
1185         this.action_id = action_id;
1186         this.options = options || {};
1187         this.am = new openerp.web.ActionManager(this);
1188     },
1189
1190     start: function() {
1191         var self = this;
1192         this.am.appendTo(this.$element.addClass('openerp'));
1193         return this.rpc("/web/action/load", { action_id: this.action_id }, function(result) {
1194             var action = result.result;
1195             action.flags = _.extend({
1196                 //views_switcher : false,
1197                 search_view : false,
1198                 action_buttons : false,
1199                 sidebar : false
1200                 //pager : false
1201             }, self.options, action.flags || {});
1202
1203             self.am.do_action(action);
1204         });
1205     },
1206
1207 });
1208
1209 openerp.web.embed = function (origin, dbname, login, key, action, options) {
1210     $('head').append($('<link>', {
1211         'rel': 'stylesheet',
1212         'type': 'text/css',
1213         'href': origin +'/web/webclient/css'
1214     }));
1215     var currentScript = document.currentScript;
1216     if (!currentScript) {
1217         var sc = document.getElementsByTagName('script');
1218         currentScript = sc[sc.length-1];
1219     }
1220     openerp.connection.bind(origin).then(function () {
1221         openerp.connection.session_authenticate(dbname, login, key, true).then(function () {
1222             var client = new openerp.web.EmbeddedClient(action, options);
1223             client.insertAfter(currentScript);
1224         });
1225     });
1226
1227 }
1228
1229 };
1230
1231 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: