[FIX] problem in database manager when db list is desactivated
[odoo/odoo.git] / addons / web / static / src / js / chrome.js
1 /*---------------------------------------------------------
2  * OpenERP Web chrome
3  *---------------------------------------------------------*/
4 openerp.web.chrome = function(instance) {
5 var QWeb = instance.web.qweb,
6     _t = instance.web._t;
7
8 instance.web.Notification =  instance.web.Widget.extend({
9     template: 'Notification',
10     init: function() {
11         this._super.apply(this, arguments);
12         instance.web.notification = this;
13     },
14     start: function() {
15         this._super.apply(this, arguments);
16         this.$el.notify({
17             speed: 500,
18             expires: 2500
19         });
20     },
21     notify: function(title, text, sticky) {
22         sticky = !!sticky;
23         var opts = {};
24         if (sticky) {
25             opts.expires = false;
26         }
27         return this.$el.notify('create', {
28             title: title,
29             text: text
30         }, opts);
31     },
32     warn: function(title, text, sticky) {
33         sticky = !!sticky;
34         var opts = {};
35         if (sticky) {
36             opts.expires = false;
37         }
38         return this.$el.notify('create', 'oe_notification_alert', {
39             title: title,
40             text: text
41         }, opts);
42     }
43 });
44
45 instance.web.action_notify = function(element, action) {
46     element.do_notify(action.params.title, action.params.text, action.params.sticky);
47 };
48 instance.web.client_actions.add("action_notify", "instance.web.action_notify");
49
50 instance.web.action_warn = function(element, action) {
51     element.do_warn(action.params.title, action.params.text, action.params.sticky);
52 };
53 instance.web.client_actions.add("action_warn", "instance.web.action_warn");
54
55 /**
56  * The very minimal function everything should call to create a dialog
57  * in OpenERP Web Client.
58  */
59 instance.web.dialog = function(element) {
60     var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
61     result.dialog("widget").openerpClass();
62     return result;
63 };
64
65 /**
66     A useful class to handle dialogs.
67
68     Attributes:
69     - $buttons: A jQuery element targeting a dom part where buttons can be added. It always exists
70     during the lifecycle of the dialog.
71 */
72 instance.web.Dialog = instance.web.Widget.extend({
73     dialog_title: "",
74     /**
75         Constructor.
76
77         @param {Widget} parent
78         @param {dictionary} options A dictionary that will be forwarded to jQueryUI Dialog. Additionaly, that
79             dictionary can contain the following keys:
80             - buttons: Deprecated. The buttons key is not propagated to jQueryUI Dialog. It must be a dictionary (key = button
81                 label, value = click handler) or a list of dictionaries (each element in the dictionary is send to the
82                 corresponding method of a jQuery element targeting the <button> tag). It is deprecated because all dialogs
83                 in OpenERP must be personalized in some way (button in red, link instead of button, ...) and this
84                 feature does not allow that kind of personalization.
85             - destroy_on_close: Default true. If true and the dialog is closed, it is automatically destroyed.
86         @param {jQuery object} content Some content to replace this.$el .
87     */
88     init: function (parent, options, content) {
89         var self = this;
90         this._super(parent);
91         this.content_to_set = content;
92         this.dialog_options = {
93             modal: true,
94             destroy_on_close: true,
95             width: 900,
96             min_width: 0,
97             max_width: '95%',
98             height: 'auto',
99             min_height: 0,
100             max_height: $(window.top).height() - 200,
101             autoOpen: false,
102             position: [false, 40],
103             buttons: null,
104             beforeClose: function () {
105                 self.trigger("closing");
106             },
107             resizeStop: function() {
108                 self.trigger("resized");
109             },
110         };
111         if (options) {
112             _.extend(this.dialog_options, options);
113         }
114         this.on("closing", this, this._closing);
115         this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><span class="oe_dialog_custom_buttons"/></div>');
116     },
117     _get_options: function() {
118         var self = this;
119         var o = _.extend({}, this.dialog_options);
120         var sizes = {
121             width: $(window.top).width(),
122             height: $(window.top).height(),
123         };
124         _.each(sizes, function(available_size, unit) {
125             o[unit] = self._get_size(o[unit], available_size);
126             o['min_' + unit] = self._get_size(o['min_' + unit] || 0, available_size);
127             o['max_' + unit] = self._get_size(o['max_' + unit] || 0, available_size);
128             if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) {
129                 o[unit] = o['min_' + unit];
130             }
131             if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) {
132                 o[unit] = o['max_' + unit];
133             }
134         });
135         o.title = o.title || this.dialog_title;
136         return o;
137     },
138     _get_size: function(val, available_size) {
139         val = val.toString();
140         if (val === 'auto') {
141             return val;
142         } else if (val.slice(-1) === "%") {
143             return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
144         } else {
145             return parseInt(val, 10);
146         }
147     },
148     renderElement: function() {
149         if (this.content_to_set) {
150             this.setElement(this.content_to_set);
151         } else if (this.template) {
152             this._super();
153         }
154     },
155     /**
156         Opens the popup. Inits the dialog if it is not already inited.
157
158         @return this
159     */
160     open: function() {
161         if (!this.dialog_inited) {
162             this.init_dialog();
163         }
164         this.$el.dialog('open');
165         this.$el.dialog("widget").append(this.$buttons);
166         return this;
167     },
168     _add_buttons: function(buttons) {
169         var self = this;
170         var $customButons = this.$buttons.find('.oe_dialog_custom_buttons').empty();
171         _.each(buttons, function(fn, text) {
172             // buttons can be object or array
173             if (!_.isFunction(fn)) {
174                 text = fn.text;
175                 fn = fn.click;
176             }
177             var $but = $(QWeb.render('WidgetButton', { widget : { string: text, node: { attrs: {} }}}));
178             $customButons.append($but);
179             $but.on('click', function(ev) {
180                 fn.call(self.$el, ev);
181             });
182         });
183     },
184     /**
185         Initializes the popup.
186
187         @return The result returned by start().
188     */
189     init_dialog: function() {
190         var options = this._get_options();
191         if (options.buttons) {
192             this._add_buttons(options.buttons);
193             delete(options.buttons);
194         }
195         this.renderElement();
196         instance.web.dialog(this.$el, options);
197         if (options.height === 'auto' && options.max_height) {
198             this.$el.css({ 'max-height': options.max_height, 'overflow-y': 'auto' });
199         }
200         this.dialog_inited = true;
201         var res = this.start();
202         return res;
203     },
204     /**
205         Closes the popup, if destroy_on_close was passed to the constructor, it is also destroyed.
206     */
207     close: function() {
208         if (this.dialog_inited && this.$el.is(":data(dialog)")) {
209             this.$el.dialog('close');
210         }
211     },
212     _closing: function() {
213         if (this.__tmp_dialog_destroying)
214             return;
215         if (this.dialog_options.destroy_on_close) {
216             this.__tmp_dialog_closing = true;
217             this.destroy();
218             this.__tmp_dialog_closing = undefined;
219         }
220     },
221     /**
222         Destroys the popup, also closes it.
223     */
224     destroy: function () {
225         this.$buttons.remove();
226         _.each(this.getChildren(), function(el) {
227             el.destroy();
228         });
229         if (! this.__tmp_dialog_closing) {
230             this.__tmp_dialog_destroying = true;
231             this.close();
232             this.__tmp_dialog_destroying = undefined;
233         }
234         if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(dialog)")) {
235             this.$el.dialog('destroy');
236         }
237         this._super();
238     }
239 });
240
241 instance.web.CrashManager = instance.web.Class.extend({
242     init: function() {
243         this.active = true;
244     },
245
246     rpc_error: function(error) {
247         if (!this.active) {
248             return;
249         }
250         // yes, exception handling is shitty
251         if (error.code === 300 && error.data && error.data.type == "client_exception" && error.data.debug.match("SessionExpiredException")) {
252             this.show_warning({type: "Session Expired", data: {
253                 fault_code: _t("Your OpenERP session expired. Please refresh the current web page.")
254             }});
255             return;
256         }
257         if (error.data.fault_code) {
258             var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
259             if (split.length > 1) {
260                 error.type = split.shift();
261                 error.data.fault_code = error.data.fault_code.substr(error.type.length + 4);
262             }
263         }
264         if (error.code === 200 && error.type) {
265             this.show_warning(error);
266         } else {
267             this.show_error(error);
268         }
269     },
270     show_warning: function(error) {
271         if (!this.active) {
272             return;
273         }
274         instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
275             title: "OpenERP " + _.str.capitalize(error.type),
276             buttons: [
277                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
278             ]
279         });
280     },
281     show_error: function(error) {
282         if (!this.active) {
283             return;
284         }
285         var buttons = {};
286         buttons[_t("Ok")] = function() {
287             $(this).dialog("close");
288         };
289         var dialog = new instance.web.Dialog(this, {
290             title: "OpenERP " + _.str.capitalize(error.type),
291             width: '80%',
292             height: '50%',
293             min_width: '800px',
294             min_height: '600px',
295             buttons: buttons
296         }).open();
297         dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error}));
298     },
299     show_message: function(exception) {
300         this.show_error({
301             type: _t("Client Error"),
302             message: exception,
303             data: {debug: ""}
304         });
305     },
306 });
307
308 instance.web.Loading = instance.web.Widget.extend({
309     template: _t("Loading"),
310     init: function(parent) {
311         this._super(parent);
312         this.count = 0;
313         this.blocked_ui = false;
314         this.session.on("request", this, this.request_call);
315         this.session.on("response", this, this.response_call);
316         this.session.on("response_failed", this, this.response_call);
317     },
318     destroy: function() {
319         this.on_rpc_event(-this.count);
320         this._super();
321     },
322     request_call: function() {
323         this.on_rpc_event(1);
324     },
325     response_call: function() {
326         this.on_rpc_event(-1);
327     },
328     on_rpc_event : function(increment) {
329         var self = this;
330         if (!this.count && increment === 1) {
331             // Block UI after 3s
332             this.long_running_timer = setTimeout(function () {
333                 self.blocked_ui = true;
334                 instance.web.blockUI();
335             }, 3000);
336         }
337
338         this.count += increment;
339         if (this.count > 0) {
340             if (instance.session.debug) {
341                 this.$el.text(_.str.sprintf( _t("Loading (%d)"), this.count));
342             } else {
343                 this.$el.text(_t("Loading"));
344             }
345             this.$el.show();
346             this.getParent().$el.addClass('oe_wait');
347         } else {
348             this.count = 0;
349             clearTimeout(this.long_running_timer);
350             // Don't unblock if blocked by somebody else
351             if (self.blocked_ui) {
352                 this.blocked_ui = false;
353                 instance.web.unblockUI();
354             }
355             this.$el.fadeOut();
356             this.getParent().$el.removeClass('oe_wait');
357         }
358     }
359 });
360
361 instance.web.DatabaseManager = instance.web.Widget.extend({
362     init: function(parent) {
363         this._super(parent);
364         this.unblockUIFunction = instance.web.unblockUI;
365         $.validator.addMethod('matches', function (s, _, re) {
366             return new RegExp(re).test(s);
367         }, _t("Invalid database name"));
368     },
369     start: function() {
370         var self = this;
371         $('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
372         var fetch_db = this.rpc("/web/database/get_list", {}).then(
373             function(result) {
374                 self.db_list = result;
375             },
376             function (_, ev) {
377                 ev.preventDefault();
378                 self.db_list = null;
379             });
380         var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) {
381             self.lang_list = result;
382         });
383         return $.when(fetch_db, fetch_langs).always(self.do_render);
384     },
385     do_render: function() {
386         var self = this;
387         instance.webclient.toggle_bars(true);
388         self.$el.html(QWeb.render("DatabaseManager", { widget : self }));
389         $('.oe_user_menu_placeholder').append(QWeb.render("DatabaseManager.user_menu",{ widget : self }));
390         $('.oe_secondary_menus_container').append(QWeb.render("DatabaseManager.menu",{ widget : self }));
391         $('ul.oe_secondary_submenu > li:first').addClass('oe_active')
392         $('ul.oe_secondary_submenu > li').bind('click', function (event) {
393             var menuitem = $(this);
394             menuitem.addClass('oe_active').siblings().removeClass('oe_active');
395             var form_id =menuitem.find('a').attr('href');
396             $(form_id).show().siblings().hide();
397             event.preventDefault();
398         });
399         $('#back-to-login').click(self.do_exit);
400         self.$el.find("td").addClass("oe_form_group_cell");
401         self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label");
402         self.$el.find("label").addClass("oe_form_label");
403         self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
404         self.$el.find("form[name=duplicate_db_form]").validate({ submitHandler: self.do_duplicate });
405         self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
406         self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
407         self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
408         self.$el.find("form[name=change_pwd_form]").validate({
409             messages: {
410                 old_pwd: _t("Please enter your previous password"),
411                 new_pwd: _t("Please enter your new password"),
412                 confirm_pwd: {
413                     required: _t("Please confirm your new password"),
414                     equalTo: _t("The confirmation does not match the password")
415                 }
416             },
417             submitHandler: self.do_change_password
418         });
419     },
420     destroy: function () {
421         this.$el.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty();
422         this._super();
423     },
424     /**
425      * Converts a .serializeArray() result into a dict. Does not bother folding
426      * multiple identical keys into an array, last key wins.
427      *
428      * @param {Array} array
429      */
430     to_object: function (array) {
431         var result = {};
432         _(array).each(function (record) {
433             result[record.name] = record.value;
434         });
435         return result;
436     },
437     /**
438      * Blocks UI and replaces $.unblockUI by a noop to prevent third parties
439      * from unblocking the UI
440      */
441     blockUI: function () {
442         instance.web.blockUI();
443         instance.web.unblockUI = function () {};
444     },
445     /**
446      * Reinstates $.unblockUI so third parties can play with blockUI, and
447      * unblocks the UI
448      */
449     unblockUI: function () {
450         instance.web.unblockUI = this.unblockUIFunction;
451         instance.web.unblockUI();
452     },
453     /**
454      * Displays an error dialog resulting from the various RPC communications
455      * failing over themselves
456      *
457      * @param {Object} error error description
458      * @param {String} error.title title of the error dialog
459      * @param {String} error.error message of the error dialog
460      */
461     display_error: function (error) {
462         return instance.web.dialog($('<div>'), {
463             modal: true,
464             title: error.title,
465             buttons: [
466                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
467             ]
468         }).html(error.error);
469     },
470     do_create: function(form) {
471         var self = this;
472         var fields = $(form).serializeArray();
473         self.rpc("/web/database/create", {'fields': fields}).done(function(result) {
474             var form_obj = self.to_object(fields);
475             var client_action = {
476                 type: 'ir.actions.client',
477                 tag: 'login',
478                 params: {
479                     'db': form_obj['db_name'],
480                     'login': 'admin',
481                     'password': form_obj['create_admin_pwd'],
482                     'login_successful': function() {
483                         var url = '/?db=' + form_obj['db_name'];
484                         if (self.session.debug) {
485                             url += '&debug';
486                         }
487                         instance.web.redirect(url);
488                     },
489                 },
490                 _push_me: false,
491             };
492             self.do_action(client_action);
493         });
494     },
495     do_duplicate: function(form) {
496         var self = this;
497         var fields = $(form).serializeArray();
498         self.rpc("/web/database/duplicate", {'fields': fields}).then(function(result) {
499             if (result.error) {
500                 self.display_error(result);
501                 return;
502             }
503             self.do_notify(_t("Duplicating database"), _t("The database has been duplicated."));
504             self.start();
505         });
506     },
507     do_drop: function(form) {
508         var self = this;
509         var $form = $(form),
510             fields = $form.serializeArray(),
511             $db_list = $form.find('[name=drop_db]'),
512             db = $db_list.val();
513         if (!db || !confirm(_.str.sprintf(_t("Do you really want to delete the database: %s ?"), db))) {
514             return;
515         }
516         self.rpc("/web/database/drop", {'fields': fields}).done(function(result) {
517             if (result.error) {
518                 self.display_error(result);
519                 return;
520             }
521             self.do_notify(_t("Dropping database"), _.str.sprintf(_t("The database %s has been dropped"), db));
522             self.start();
523         });
524     },
525     do_backup: function(form) {
526         var self = this;
527         self.blockUI();
528         self.session.get_file({
529             form: form,
530             success: function () {
531                 self.do_notify(_t("Backed"), _t("Database backed up successfully"));
532             },
533             error: function(error){
534                if(error){
535                   self.display_error({
536                         title: _t("Backup Database"),
537                         error: 'AccessDenied'
538                   });
539                }
540             },
541             complete: function() {
542                 self.unblockUI();
543             }
544         });
545     },
546     do_restore: function(form) {
547         var self = this;
548         self.blockUI();
549         $(form).ajaxSubmit({
550             url: '/web/database/restore',
551             type: 'POST',
552             resetForm: true,
553             success: function (body) {
554                 // If empty body, everything went fine
555                 if (!body) { return; }
556
557                 if (body.indexOf('403 Forbidden') !== -1) {
558                     self.display_error({
559                         title: _t("Access Denied"),
560                         error: _t("Incorrect super-administrator password")
561                     });
562                 } else {
563                     self.display_error({
564                         title: _t("Restore Database"),
565                         error: _t("Could not restore the database")
566                     });
567                 }
568             },
569             complete: function() {
570                 self.unblockUI();
571                 self.do_notify(_t("Restored"), _t("Database restored successfully"));
572             }
573         });
574     },
575     do_change_password: function(form) {
576         var self = this;
577         self.rpc("/web/database/change_password", {
578             'fields': $(form).serializeArray()
579         }).done(function(result) {
580             if (result.error) {
581                 self.display_error(result);
582                 return;
583             }
584             self.unblockUI();
585             self.do_notify(_t("Changed Password"), _t("Password has been changed successfully"));
586         });
587     },
588     do_exit: function () {
589         this.$el.remove();
590         instance.webclient.show_login();
591     }
592 });
593 instance.web.client_actions.add("database_manager", "instance.web.DatabaseManager");
594
595 instance.web.Login =  instance.web.Widget.extend({
596     template: "Login",
597     remember_credentials: true,
598     events: {
599         'change input[name=db],select[name=db]': function(ev) {
600             this.set('database_selector', $(ev.currentTarget).val());
601         },
602     },
603
604     init: function(parent, action) {
605         this._super(parent);
606         this.has_local_storage = typeof(localStorage) != 'undefined';
607         this.db_list = null;
608         this.selected_db = null;
609         this.selected_login = null;
610         this.params = action.params || {};
611         if (_.isEmpty(this.params)) {
612             this.params = $.bbq.getState(true);
613         }
614         if (action && action.params && action.params.db) {
615             this.params.db = action.params.db;
616         } else if ($.deparam.querystring().db) {
617             this.params.db = $.deparam.querystring().db;
618         }
619         if (this.params.db) {
620             this.selected_db = this.params.db;
621         }
622
623         if (this.params.login_successful) {
624             this.on('login_successful', this, this.params.login_successful);
625         }
626     },
627     start: function() {
628         var self = this;
629         self.$el.find("form").submit(self.on_submit);
630         self.$el.find('.oe_login_manage_db').click(function() {
631             self.do_action("database_manager");
632         });
633         self.on('change:database_selector', this, function() {
634             this.database_selected(this.get('database_selector'));
635         });
636         var d = $.when();
637         if ($.param.fragment().token) {
638             self.params.token = $.param.fragment().token;
639         }
640         // used by dbmanager.do_create via internal client action
641         if (self.params.db && self.params.login && self.params.password) {
642             d = self.do_login(self.params.db, self.params.login, self.params.password);
643         } else {
644             d = self.rpc("/web/database/get_list", {})
645                 .done(self.on_db_loaded)
646                 .fail(self.on_db_failed)
647                 .always(function() {
648                     if (self.selected_db && self.has_local_storage && self.remember_credentials) {
649                         self.$("[name=login]").val(localStorage.getItem(self.selected_db + '|last_login') || '');
650                         if (self.session.debug) {
651                             self.$("[name=password]").val(localStorage.getItem(self.selected_db + '|last_password') || '');
652                         }
653                     }
654                 });
655         }
656         return d;
657     },
658     remember_last_used_database: function(db) {
659         // This cookie will be used server side in order to avoid db reloading on first visit
660         var ttl = 24 * 60 * 60 * 365;
661         document.cookie = [
662             'last_used_database=' + db,
663             'path=/',
664             'max-age=' + ttl,
665             'expires=' + new Date(new Date().getTime() + ttl * 1000).toGMTString()
666         ].join(';');
667     },
668     database_selected: function(db) {
669         var params = $.deparam.querystring();
670         params.db = db;
671         this.remember_last_used_database(db);
672         this.$('.oe_login_dbpane').empty().text(_t('Loading...'));
673         this.$('[name=login], [name=password]').prop('readonly', true);
674         instance.web.redirect('/?' + $.param(params));
675     },
676     on_db_loaded: function (result) {
677         var self = this;
678         this.db_list = result;
679         if (!this.selected_db) {
680             this.selected_db = result[0];
681         }
682         this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
683         if(this.db_list.length === 0) {
684             this.do_action("database_manager");
685         } else if(this.db_list.length === 1) {
686             this.$('div.oe_login_dbpane').hide();
687         } else {
688             this.$('div.oe_login_dbpane').show();
689         }
690     },
691     on_db_failed: function (error, event) {
692         if (error.data.fault_code === 'AccessDenied') {
693             event.preventDefault();
694         }
695     },
696     on_submit: function(ev) {
697         if(ev) {
698             ev.preventDefault();
699         }
700         var db = this.$("form [name=db]").val();
701         if (!db) {
702             this.do_warn(_t("Login"), _t("No database selected !"));
703             return false;
704         }
705         var login = this.$("form input[name=login]").val();
706         var password = this.$("form input[name=password]").val();
707
708         this.do_login(db, login, password);
709     },
710     /**
711      * Performs actual login operation, and UI-related stuff
712      *
713      * @param {String} db database to log in
714      * @param {String} login user login
715      * @param {String} password user password
716      */
717     do_login: function (db, login, password) {
718         var self = this;
719         self.hide_error();
720         self.$(".oe_login_pane").fadeOut("slow");
721         return this.session.session_authenticate(db, login, password).then(function() {
722             self.remember_last_used_database(db);
723             if (self.has_local_storage && self.remember_credentials) {
724                 localStorage.setItem(db + '|last_login', login);
725                 if (self.session.debug) {
726                     localStorage.setItem(db + '|last_password', password);
727                 }
728             }
729             self.trigger('login_successful');
730         }, function () {
731             self.$(".oe_login_pane").fadeIn("fast", function() {
732                 self.show_error(_t("Invalid username or password"));
733             });
734         });
735     },
736     show_error: function(message) {
737         this.$el.addClass("oe_login_invalid");
738         this.$(".oe_login_error_message").text(message);
739     },
740     hide_error: function() {
741         this.$el.removeClass('oe_login_invalid');
742     },
743 });
744 instance.web.client_actions.add("login", "instance.web.Login");
745
746
747 /**
748  * Redirect to url by replacing window.location
749  * If wait is true, sleep 1s and wait for the server i.e. after a restart.
750  */
751 instance.web.redirect = function(url, wait) {
752     // Dont display a dialog if some xmlhttprequest are in progress
753     if (instance.client && instance.client.crashmanager) {
754         instance.client.crashmanager.active = false;
755     }
756
757     var wait_server = function() {
758         instance.session.rpc("/web/webclient/version_info", {}).done(function() {
759             window.location = url;
760         }).fail(function() {
761             setTimeout(wait_server, 250);
762         });
763     };
764
765     if (wait) {
766         setTimeout(wait_server, 1000);
767     } else {
768         window.location = url;
769     }
770 };
771
772 /**
773  * Client action to reload the whole interface.
774  * If params.menu_id, it opens the given menu entry.
775  * If params.wait, reload will wait the openerp server to be reachable before reloading
776  */
777 instance.web.Reload = function(parent, action) {
778     var params = action.params || {};
779     var menu_id = params.menu_id || false;
780     var l = window.location;
781
782     var sobj = $.deparam(l.search.substr(1));
783     sobj.ts = new Date().getTime();
784     if (params.url_search) {
785         sobj = _.extend(sobj, params.url_search);
786     }
787     var search = '?' + $.param(sobj);
788
789     var hash = l.hash;
790     if (menu_id) {
791         hash = "#menu_id=" + menu_id;
792     }
793     var url = l.protocol + "//" + l.host + l.pathname + search + hash;
794
795     instance.web.redirect(url, params.wait);
796 };
797 instance.web.client_actions.add("reload", "instance.web.Reload");
798
799 /**
800  * Client action to go back in breadcrumb history.
801  * If can't go back in history stack, will go back to home.
802  */
803 instance.web.HistoryBack = function(parent) {
804     if (!parent.history_back()) {
805         instance.web.Home(parent);
806     }
807 };
808 instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
809
810 /**
811  * Client action to go back home.
812  */
813 instance.web.Home = function(parent, action) {
814     var url = '/' + (window.location.search || '');
815     instance.web.redirect(url, action.params && action.params.wait);
816 };
817 instance.web.client_actions.add("home", "instance.web.Home");
818
819 instance.web.ChangePassword =  instance.web.Widget.extend({
820     template: "ChangePassword",
821     start: function() {
822         var self = this;
823         this.getParent().dialog_title = _t("Change Password");
824         var $button = self.$el.find('.oe_form_button');
825         $button.appendTo(this.getParent().$buttons);
826         $button.eq(2).click(function(){
827            self.getParent().close();
828         })
829         $button.eq(0).click(function(){
830           self.rpc("/web/session/change_password",{
831                'fields': $("form[name=change_password_form]").serializeArray()
832           }).done(function(result) {
833                if (result.error) {
834                   self.display_error(result);
835                   return;
836                } else {
837                    instance.webclient.on_logout();
838                }
839           });
840        })
841     },
842     display_error: function (error) {
843         return instance.web.dialog($('<div>'), {
844             modal: true,
845             title: error.title,
846             buttons: [
847                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
848             ]
849         }).html(error.error);
850     },
851 })
852 instance.web.client_actions.add("change_password", "instance.web.ChangePassword");
853
854 instance.web.Menu =  instance.web.Widget.extend({
855     template: 'Menu',
856     init: function() {
857         var self = this;
858         this._super.apply(this, arguments);
859         this.has_been_loaded = $.Deferred();
860         this.maximum_visible_links = 'auto'; // # of menu to show. 0 = do not crop, 'auto' = algo
861         this.data = {data:{children:[]}};
862         this.on("menu_loaded", this, function (menu_data) {
863             self.reflow();
864             // launch the fetch of needaction counters, asynchronous
865             if (!_.isEmpty(menu_data.all_menu_ids)) {
866                 this.do_load_needaction(menu_data.all_menu_ids);
867             }
868         });
869         var lazyreflow = _.debounce(this.reflow.bind(this), 200);
870         instance.web.bus.on('resize', this, function() {
871             self.$el.height(0);
872             lazyreflow();
873         });
874     },
875     start: function() {
876         this._super.apply(this, arguments);
877         this.$secondary_menus = this.getParent().$el.find('.oe_secondary_menus_container');
878         this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
879         return this.do_reload();
880     },
881     do_reload: function() {
882         var self = this;
883         return this.rpc("/web/menu/load", {}).done(function(r) {
884             self.menu_loaded(r);
885         });
886     },
887     menu_loaded: function(data) {
888         var self = this;
889         this.data = {data: data};
890         this.renderElement();
891         this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this }));
892         this.$el.on('click', 'a[data-menu]', this.on_top_menu_click);
893         // Hide second level submenus
894         this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide();
895         if (self.current_menu) {
896             self.open_menu(self.current_menu);
897         }
898         this.trigger('menu_loaded', data);
899         this.has_been_loaded.resolve();
900     },
901     do_load_needaction: function (menu_ids) {
902         var self = this;
903         menu_ids = _.compact(menu_ids);
904         if (_.isEmpty(menu_ids)) {
905             return $.when();
906         }
907         return this.rpc("/web/menu/load_needaction", {'menu_ids': menu_ids}).done(function(r) {
908             self.on_needaction_loaded(r);
909         });
910     },
911     on_needaction_loaded: function(data) {
912         var self = this;
913         this.needaction_data = data;
914         _.each(this.needaction_data, function (item, menu_id) {
915             var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]');
916             $item.find('.oe_menu_counter').remove();
917             if (item.needaction_counter && item.needaction_counter > 0) {
918                 $item.append(QWeb.render("Menu.needaction_counter", { widget : item }));
919             }
920         });
921     },
922     /**
923      * Reflow the menu items and dock overflowing items into a "More" menu item.
924      * Automatically called when 'menu_loaded' event is triggered and on window resizing.
925      */
926     reflow: function() {
927         var self = this;
928         this.$el.height('auto').show();
929         var $more_container = this.$('.oe_menu_more_container').hide();
930         var $more = this.$('.oe_menu_more');
931         $more.children('li').insertBefore($more_container);
932         var $toplevel_items = this.$el.children('li').not($more_container).hide();
933         $toplevel_items.each(function() {
934             var remaining_space = self.$el.parent().width() - $more_container.outerWidth();
935             self.$el.parent().children(':visible').each(function() {
936                 remaining_space -= $(this).outerWidth();
937             });
938             if ($(this).width() > remaining_space) {
939                 return false;
940             }
941             $(this).show();
942         });
943         $more.append($toplevel_items.filter(':hidden').show());
944         $more_container.toggle(!!$more.children().length);
945         // Hide toplevel item if there is only one
946         var $toplevel = this.$el.children("li:visible");
947         if ($toplevel.length === 1) {
948             $toplevel.hide();
949         }
950     },
951     /**
952      * Opens a given menu by id, as if a user had browsed to that menu by hand
953      * except does not trigger any event on the way
954      *
955      * @param {Number} id database id of the terminal menu to select
956      */
957     open_menu: function (id) {
958         this.current_menu = id;
959         this.session.active_id = id;
960         var $clicked_menu, $sub_menu, $main_menu;
961         $clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']');
962         this.trigger('open_menu', id, $clicked_menu);
963
964         if (this.$secondary_menus.has($clicked_menu).length) {
965             $sub_menu = $clicked_menu.parents('.oe_secondary_menu');
966             $main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
967         } else {
968             $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
969             $main_menu = $clicked_menu;
970         }
971
972         // Activate current main menu
973         this.$el.find('.oe_active').removeClass('oe_active');
974         $main_menu.addClass('oe_active');
975
976         // Show current sub menu
977         this.$secondary_menus.find('.oe_secondary_menu').hide();
978         $sub_menu.show();
979
980         // Hide/Show the leftbar menu depending of the presence of sub-items
981         this.$secondary_menus.parent('.oe_leftbar').toggle(!!$sub_menu.children().length);
982
983         // Activate current menu item and show parents
984         this.$secondary_menus.find('.oe_active').removeClass('oe_active');
985         if ($main_menu !== $clicked_menu) {
986             $clicked_menu.parents().show();
987             if ($clicked_menu.is('.oe_menu_toggler')) {
988                 $clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggle();
989             } else {
990                 $clicked_menu.parent().addClass('oe_active');
991             }
992         }
993     },
994     /**
995      * Call open_menu with the first menu_item matching an action_id
996      *
997      * @param {Number} id the action_id to match
998      */
999     open_action: function (id) {
1000         var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]');
1001         var menu_id = $menu.data('menu');
1002         if (menu_id) {
1003             this.open_menu(menu_id);
1004         }
1005     },
1006     /**
1007      * Process a click on a menu item
1008      *
1009      * @param {Number} id the menu_id
1010      * @param {Boolean} [needaction=false] whether the triggered action should execute in a `needs action` context
1011      */
1012     menu_click: function(id, needaction) {
1013         if (!id) { return; }
1014
1015         // find back the menuitem in dom to get the action
1016         var $item = this.$el.find('a[data-menu=' + id + ']');
1017         if (!$item.length) {
1018             $item = this.$secondary_menus.find('a[data-menu=' + id + ']');
1019         }
1020         var action_id = $item.data('action-id');
1021         // If first level menu doesnt have action trigger first leaf
1022         if (!action_id) {
1023             if(this.$el.has($item).length) {
1024                 var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']');
1025                 var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]');
1026                 if($items.length) {
1027                     action_id = $items.data('action-id');
1028                     id = $items.data('menu');
1029                 }
1030             }
1031         }
1032         if (action_id) {
1033             this.trigger('menu_click', {
1034                 action_id: action_id,
1035                 needaction: needaction,
1036                 id: id,
1037                 previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu)
1038             }, $item);
1039         }
1040         this.open_menu(id);
1041     },
1042     do_reload_needaction: function () {
1043         var self = this;
1044         if (self.current_menu) {
1045             self.do_load_needaction([self.current_menu]).then(function () {
1046                 self.trigger("need_action_reloaded");
1047             });
1048         }
1049     },
1050     /**
1051      * Jquery event handler for menu click
1052      *
1053      * @param {Event} ev the jquery event
1054      */
1055     on_top_menu_click: function(ev) {
1056         var self = this;
1057         var id = $(ev.currentTarget).data('menu');
1058         var menu_ids = [id];
1059         var menu = _.filter(this.data.data.children, function (menu) {return menu.id == id;})[0];
1060         function add_menu_ids (menu) {
1061             if (menu.children) {
1062                 _.each(menu.children, function (menu) {
1063                     menu_ids.push(menu.id);
1064                     add_menu_ids(menu);
1065                 });
1066             }
1067         };
1068         add_menu_ids(menu);
1069         self.do_load_needaction(menu_ids).then(function () {
1070             self.trigger("need_action_reloaded");
1071         });
1072         this.on_menu_click(ev);
1073     },
1074     on_menu_click: function(ev) {
1075         ev.preventDefault();
1076         var needaction = $(ev.target).is('div.oe_menu_counter');
1077         this.menu_click($(ev.currentTarget).data('menu'), needaction);
1078     },
1079 });
1080
1081 instance.web.UserMenu =  instance.web.Widget.extend({
1082     template: "UserMenu",
1083     init: function(parent) {
1084         this._super(parent);
1085         this.update_promise = $.Deferred().resolve();
1086     },
1087     start: function() {
1088         var self = this;
1089         this._super.apply(this, arguments);
1090         this.$el.on('click', '.oe_dropdown_menu li a[data-menu]', function(ev) {
1091             ev.preventDefault();
1092             var f = self['on_menu_' + $(this).data('menu')];
1093             if (f) {
1094                 f($(this));
1095             }
1096         });
1097     },
1098     do_update: function () {
1099         var self = this;
1100         var fct = function() {
1101             var $avatar = self.$el.find('.oe_topbar_avatar');
1102             $avatar.attr('src', $avatar.data('default-src'));
1103             if (!self.session.uid)
1104                 return;
1105             var func = new instance.web.Model("res.users").get_func("read");
1106             return self.alive(func(self.session.uid, ["name", "company_id"])).then(function(res) {
1107                 var topbar_name = res.name;
1108                 if(instance.session.debug)
1109                     topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.session.db);
1110                 if(res.company_id[0] > 1)
1111                     topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]);
1112                 self.$el.find('.oe_topbar_name').text(topbar_name);
1113                 if (!instance.session.debug) {
1114                     topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.session.db);
1115                 }
1116                 var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
1117                 $avatar.attr('src', avatar_src);
1118             });
1119         };
1120         this.update_promise = this.update_promise.then(fct, fct);
1121     },
1122     on_menu_help: function() {
1123         window.open('http://help.openerp.com', '_blank');
1124     },
1125     on_menu_logout: function() {
1126         this.trigger('user_logout');
1127     },
1128     on_menu_settings: function() {
1129         var self = this;
1130         if (!this.getParent().has_uncommitted_changes()) {
1131             self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
1132                 result.res_id = instance.session.uid;
1133                 self.getParent().action_manager.do_action(result);
1134             });
1135         }
1136     },
1137     on_menu_about: function() {
1138         var self = this;
1139         self.rpc("/web/webclient/version_info", {}).done(function(res) {
1140             var $help = $(QWeb.render("UserMenu.about", {version_info: res}));
1141             $help.find('a.oe_activate_debug_mode').click(function (e) {
1142                 e.preventDefault();
1143                 window.location = $.param.querystring( window.location.href, 'debug');
1144             });
1145             instance.web.dialog($help, {autoOpen: true,
1146                 modal: true, width: 507, height: 290, resizable: false, title: _t("About")});
1147         });
1148     },
1149 });
1150
1151 instance.web.Client = instance.web.Widget.extend({
1152     init: function(parent, origin) {
1153         instance.client = instance.webclient = this;
1154         this._super(parent);
1155         this.origin = origin;
1156     },
1157     start: function() {
1158         var self = this;
1159         return instance.session.session_bind(this.origin).then(function() {
1160             var $e = $(QWeb.render(self._template, {widget: self}));
1161             self.replaceElement($e);
1162             $e.openerpClass();
1163             self.bind_events();
1164             return self.show_common();
1165         });
1166     },
1167     bind_events: function() {
1168         var self = this;
1169         this.$el.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() {
1170             $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter');
1171         });
1172         this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
1173             ev.preventDefault();
1174             var $toggle = $(this);
1175             var $menu = $toggle.siblings('.oe_dropdown_menu');
1176             $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
1177             var state = $menu.is('.oe_opened');
1178             setTimeout(function() {
1179                 // Do not alter propagation
1180                 $toggle.add($menu).toggleClass('oe_opened', !state);
1181                 if (!state) {
1182                     // Move $menu if outside window's edge
1183                     var doc_width = $(document).width();
1184                     var offset = $menu.offset();
1185                     var menu_width = $menu.width();
1186                     var x = doc_width - offset.left - menu_width - 2;
1187                     if (x < 0) {
1188                         $menu.offset({ left: offset.left + x }).width(menu_width);
1189                     }
1190                 }
1191             }, 0);
1192         });
1193         instance.web.bus.on('click', this, function(ev) {
1194             $.fn.tipsy.clear();
1195             if (!$(ev.target).is('input[type=file]')) {
1196                 self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
1197             }
1198         });
1199     },
1200     show_common: function() {
1201         var self = this;
1202         this.crashmanager =  new instance.web.CrashManager();
1203         instance.session.on('error', this.crashmanager, this.crashmanager.rpc_error);
1204         self.notification = new instance.web.Notification(this);
1205         self.notification.appendTo(self.$el);
1206         self.loading = new instance.web.Loading(self);
1207         self.loading.appendTo(self.$el);
1208         self.action_manager = new instance.web.ActionManager(self);
1209         self.action_manager.appendTo(self.$('.oe_application'));
1210     },
1211     toggle_bars: function(value) {
1212         this.$('tr:has(td.oe_topbar),.oe_leftbar').toggle(value);
1213     },
1214     has_uncommitted_changes: function() {
1215         return false;
1216     },
1217 });
1218
1219 instance.web.WebClient = instance.web.Client.extend({
1220     _template: 'WebClient',
1221     events: {
1222         'click .oe_logo_edit_admin': 'logo_edit'
1223     },
1224     init: function(parent) {
1225         this._super(parent);
1226         this._current_state = null;
1227         this.menu_dm = new instance.web.DropMisordered();
1228         this.action_mutex = new $.Mutex();
1229     },
1230     start: function() {
1231         var self = this;
1232         return $.when(this._super()).then(function() {
1233             if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
1234                 $("body").addClass("kitten-mode-activated");
1235                 $("body").css("background-image", "url(" + instance.session.origin + "/web/static/src/img/back-enable.jpg" + ")");
1236                 if ($.blockUI) {
1237                     $.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">';
1238                 }
1239             }
1240             if (!self.session.session_is_valid()) {
1241                 self.show_login();
1242             } else {
1243                 self.show_application();
1244             }
1245         });
1246     },
1247     set_title: function(title) {
1248         title = _.str.clean(title);
1249         var sep = _.isEmpty(title) ? '' : ' - ';
1250         document.title = title + sep + 'OpenERP';
1251     },
1252     show_common: function() {
1253         var self = this;
1254         this._super();
1255         window.onerror = function (message, file, line) {
1256             self.crashmanager.show_error({
1257                 type: _t("Client Error"),
1258                 message: message,
1259                 data: {debug: file + ':' + line}
1260             });
1261         };
1262     },
1263     show_login: function() {
1264         this.toggle_bars(false);
1265
1266         var state = $.bbq.getState(true);
1267         var action = {
1268             type: 'ir.actions.client',
1269             tag: 'login',
1270             _push_me: false,
1271         };
1272
1273         this.action_manager.do_action(action);
1274         this.action_manager.inner_widget.on('login_successful', this, function() {
1275             this.show_application();        // will load the state we just pushed
1276         });
1277     },
1278     show_application: function() {
1279         var self = this;
1280         self.toggle_bars(true);
1281         self.update_logo();
1282         self.menu = new instance.web.Menu(self);
1283         self.menu.replace(this.$el.find('.oe_menu_placeholder'));
1284         self.menu.on('menu_click', this, this.on_menu_action);
1285         self.user_menu = new instance.web.UserMenu(self);
1286         self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder'));
1287         self.user_menu.on('user_logout', self, self.on_logout);
1288         self.user_menu.do_update();
1289         self.bind_hashchange();
1290         self.set_title();
1291         self.check_timezone();
1292     },
1293     update_logo: function() {
1294         var img = this.session.url('/web/binary/company_logo');
1295         this.$('.oe_logo img').attr('src', '').attr('src', img);
1296         this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', this.session.uid === 1);
1297     },
1298     logo_edit: function(ev) {
1299         var self = this;
1300         self.alive(new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
1301             self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
1302                 result.res_id = res['company_id'][0];
1303                 result.target = "new";
1304                 result.views = [[false, 'form']];
1305                 result.flags = {
1306                     action_buttons: true,
1307                 };
1308                 self.action_manager.do_action(result);
1309                 var form = self.action_manager.dialog_widget.views.form.controller;
1310                 form.on("on_button_cancel", self.action_manager.dialog, self.action_manager.dialog.close);
1311                 form.on('record_saved', self, function() {
1312                     self.action_manager.dialog.close();
1313                     self.update_logo();
1314                 });
1315             });
1316         });
1317         return false;
1318     },
1319     check_timezone: function() {
1320         var self = this;
1321         return self.alive(new instance.web.Model('res.users').call('read', [[this.session.uid], ['tz_offset']])).then(function(result) {
1322             var user_offset = result[0]['tz_offset'];
1323             var offset = -(new Date().getTimezoneOffset());
1324             // _.str.sprintf()'s zero front padding is buggy with signed decimals, so doing it manually
1325             var browser_offset = (offset < 0) ? "-" : "+";
1326             browser_offset += _.str.sprintf("%02d", Math.abs(offset / 60));
1327             browser_offset += _.str.sprintf("%02d", Math.abs(offset % 60));
1328             if (browser_offset !== user_offset) {
1329                 var $icon = $(QWeb.render('WebClient.timezone_systray'));
1330                 $icon.on('click', function() {
1331                     var notification = self.do_warn(_t("Timezone mismatch"), QWeb.render('WebClient.timezone_notification', {
1332                         user_timezone: instance.session.user_context.tz || 'UTC',
1333                         user_offset: user_offset,
1334                         browser_offset: browser_offset,
1335                     }), true);
1336                     notification.element.find('.oe_webclient_timezone_notification').on('click', function() {
1337                         notification.close();
1338                     }).find('a').on('click', function() {
1339                         notification.close();
1340                         self.user_menu.on_menu_settings();
1341                         return false;
1342                     });
1343                 });
1344                 $icon.appendTo(self.$('.oe_systray'));
1345             }
1346         });
1347     },
1348     destroy_content: function() {
1349         _.each(_.clone(this.getChildren()), function(el) {
1350             el.destroy();
1351         });
1352         this.$el.children().remove();
1353     },
1354     do_reload: function() {
1355         var self = this;
1356         return this.session.session_reload().then(function () {
1357             instance.session.load_modules(true).then(
1358                 self.menu.proxy('do_reload')); });
1359
1360     },
1361     do_notify: function() {
1362         var n = this.notification;
1363         return n.notify.apply(n, arguments);
1364     },
1365     do_warn: function() {
1366         var n = this.notification;
1367         return n.warn.apply(n, arguments);
1368     },
1369     on_logout: function() {
1370         var self = this;
1371         if (!this.has_uncommitted_changes()) {
1372             this.session.session_logout().done(function () {
1373                 $(window).unbind('hashchange', self.on_hashchange);
1374                 self.do_push_state({});
1375                 window.location.reload();
1376             });
1377         }
1378     },
1379     bind_hashchange: function() {
1380         var self = this;
1381         $(window).bind('hashchange', this.on_hashchange);
1382
1383         var state = $.bbq.getState(true);
1384         if (_.isEmpty(state) || state.action == "login") {
1385             self.menu.has_been_loaded.done(function() {
1386                 var first_menu_id = self.menu.$el.find("a:first").data("menu");
1387                 if(first_menu_id) {
1388                     self.menu.menu_click(first_menu_id);
1389                 }
1390             });
1391         } else {
1392             $(window).trigger('hashchange');
1393         }
1394     },
1395     on_hashchange: function(event) {
1396         var self = this;
1397         var stringstate = event.getState(false);
1398         if (!_.isEqual(this._current_state, stringstate)) {
1399             var state = event.getState(true);
1400             if(!state.action && state.menu_id) {
1401                 self.menu.has_been_loaded.done(function() {
1402                     self.menu.do_reload().done(function() {
1403                         self.menu.menu_click(state.menu_id);
1404                     });
1405                 });
1406             } else {
1407                 state._push_me = false;  // no need to push state back...
1408                 this.action_manager.do_load_state(state, !!this._current_state);
1409             }
1410         }
1411         this._current_state = stringstate;
1412     },
1413     do_push_state: function(state) {
1414         this.set_title(state.title);
1415         delete state.title;
1416         var url = '#' + $.param(state);
1417         this._current_state = $.deparam($.param(state), false);     // stringify all values
1418         $.bbq.pushState(url);
1419         this.trigger('state_pushed', state);
1420     },
1421     on_menu_action: function(options) {
1422         var self = this;
1423         return this.menu_dm.add(this.rpc("/web/action/load", { action_id: options.action_id }))
1424             .then(function (result) {
1425                 return self.action_mutex.exec(function() {
1426                     if (options.needaction) {
1427                         result.context = new instance.web.CompoundContext(result.context, {
1428                             search_default_message_unread: true,
1429                             search_disable_custom_filters: true,
1430                         });
1431                     }
1432                     var completed = $.Deferred();
1433                     $.when(self.action_manager.do_action(result, {
1434                         clear_breadcrumbs: true,
1435                         action_menu_id: self.menu.current_menu,
1436                     })).fail(function() {
1437                         self.menu.open_menu(options.previous_menu_id);
1438                     }).always(function() {
1439                         completed.resolve();
1440                     });
1441                     setTimeout(function() {
1442                         completed.resolve();
1443                     }, 2000);
1444                     // We block the menu when clicking on an element until the action has correctly finished
1445                     // loading. If something crash, there is a 2 seconds timeout before it's unblocked.
1446                     return completed;
1447                 });
1448             });
1449     },
1450     set_content_full_screen: function(fullscreen) {
1451         $(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll');
1452         this.$('.oe_webclient').toggleClass(
1453             'oe_content_full_screen', fullscreen);
1454     },
1455     has_uncommitted_changes: function() {
1456         var $e = $.Event('clear_uncommitted_changes');
1457         instance.web.bus.trigger('clear_uncommitted_changes', $e);
1458         if ($e.isDefaultPrevented()) {
1459             return true;
1460         } else {
1461             return this._super.apply(this, arguments);
1462         }
1463     },
1464 });
1465
1466 instance.web.EmbeddedClient = instance.web.Client.extend({
1467     _template: 'EmbedClient',
1468     init: function(parent, origin, dbname, login, key, action_id, options) {
1469         this._super(parent, origin);
1470         this.bind_credentials(dbname, login, key);
1471         this.action_id = action_id;
1472         this.options = options || {};
1473     },
1474     start: function() {
1475         var self = this;
1476         return $.when(this._super()).then(function() {
1477             return self.authenticate().then(function() {
1478                 if (!self.action_id) {
1479                     return;
1480                 }
1481                 return self.rpc("/web/action/load", { action_id: self.action_id }).done(function(result) {
1482                     var action = result;
1483                     action.flags = _.extend({
1484                         //views_switcher : false,
1485                         search_view : false,
1486                         action_buttons : false,
1487                         sidebar : false
1488                         //pager : false
1489                     }, self.options, action.flags || {});
1490
1491                     self.do_action(action);
1492                 });
1493             });
1494         });
1495     },
1496
1497     do_action: function(/*...*/) {
1498         var am = this.action_manager;
1499         return am.do_action.apply(am, arguments);
1500     },
1501
1502     authenticate: function() {
1503         var s = instance.session;
1504         if (s.session_is_valid() && s.db === this.dbname && s.login === this.login) {
1505             return $.when();
1506         }
1507         return instance.session.session_authenticate(this.dbname, this.login, this.key, true);
1508     },
1509
1510     bind_credentials: function(dbname, login, key) {
1511         this.dbname = dbname;
1512         this.login = login;
1513         this.key = key;
1514     },
1515
1516 });
1517
1518 instance.web.embed = function (origin, dbname, login, key, action, options) {
1519     $('head').append($('<link>', {
1520         'rel': 'stylesheet',
1521         'type': 'text/css',
1522         'href': origin +'/web/webclient/css'
1523     }));
1524     var currentScript = document.currentScript;
1525     if (!currentScript) {
1526         var sc = document.getElementsByTagName('script');
1527         currentScript = sc[sc.length-1];
1528     }
1529     var client = new instance.web.EmbeddedClient(null, origin, dbname, login, key, action, options);
1530     client.insertAfter(currentScript);
1531 };
1532
1533 };
1534
1535 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: