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