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