summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/web_client.js
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2015-03-09 16:34:27 +0100
committerGéry Debongnie <ged@odoo.com>2015-03-18 09:01:49 +0100
commite8a00bc50da435d3859118ca127d20a2eb9a0cab (patch)
tree039f6ec40b37392e76c8830a8e518ac40f7e7fec /addons/web/static/src/js/web_client.js
parent9036084f065c7aca3b2199903f4da67db3501ba7 (diff)
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code. Instead of using global (namespaced) variables everywhere (like openerp.web.whatever, ...), each file declare one (or more) module with its dependencies. Each module will be loaded after its dependencies, and will be given a reference to the return values. This change is a massive departure from the way the client worked previously. It will sadly break some/most history, but I am sure that the future git ecosystem will allow you to easily browse the history past this revision (one can dream). This commit feature: * main namespace is now odoo instead of openerp * a way to define modules and dependencies (inspired by requirejs) * many files have been splitted in simpler subfiles * basic support for debugging in odoo.__DEBUG__ * no global variable (i.e. nothing in window.openerp) * many bug fixes, many new bugs Future vision: this modularization should simplify future code development, simplify debugging and will allow a serious testing effort
Diffstat (limited to 'addons/web/static/src/js/web_client.js')
-rw-r--r--addons/web/static/src/js/web_client.js368
1 files changed, 368 insertions, 0 deletions
diff --git a/addons/web/static/src/js/web_client.js b/addons/web/static/src/js/web_client.js
new file mode 100644
index 00000000000..e4d943ce6bf
--- /dev/null
+++ b/addons/web/static/src/js/web_client.js
@@ -0,0 +1,368 @@
+odoo.define('web.WebClient', ['web.ActionManager', 'web.core', 'web.crash_manager', 'web.data', 'web.Loading', 'web.Menu', 'web.Model', 'web.Notification', 'web.session', 'web.SystrayMenu', 'web.UserMenu', 'web.utils', 'web.Widget'], function (require) {
+"use strict";
+
+var ActionManager = require('web.ActionManager');
+var core = require('web.core');
+var crash_manager = require('web.crash_manager');
+var data = require('web.data');
+var Loading = require('web.Loading');
+var Menu = require('web.Menu');
+var Model = require('web.Model');
+var Notification = require('web.Notification');
+var session = require('web.session');
+var SystrayMenu = require('web.SystrayMenu');
+var UserMenu = require('web.UserMenu');
+var utils = require('web.utils');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+
+var WebClient = Widget.extend({
+ events: {
+ 'click .oe_logo_edit_admin': 'logo_edit',
+ 'click .oe_logo img': 'on_logo_click',
+ },
+
+ init: function(parent, client_options) {
+ this.client_options = {};
+ this._super(parent);
+ this.origin = undefined;
+ if (client_options) {
+ _.extend(this.client_options, client_options);
+ }
+ this._current_state = null;
+ this.menu_dm = new utils.DropMisordered();
+ this.action_mutex = new utils.Mutex();
+ this.set('title_part', {"zopenerp": "Odoo"});
+ },
+ start: function() {
+ var self = this;
+ this.on("change:title_part", this, this._title_changed);
+ this._title_changed();
+
+ core.bus.on('web_client_toggle_bars', this, function () {
+ this.toggle_bars.apply(this, arguments);
+ });
+
+ return session.session_bind(this.origin).then(function() {
+ self.bind_events();
+ return self.show_common();
+ }).then(function() {
+ if (session.session_is_valid()) {
+ self.show_application();
+ }
+ if (self.client_options.action) {
+ self.action_manager.do_action(self.client_options.action);
+ delete(self.client_options.action);
+ }
+ self.trigger('web_client_ready');
+ });
+ },
+ bind_events: function() {
+ var self = this;
+ $('.oe_systray').show();
+ this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
+ $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
+ });
+ this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
+ ev.preventDefault();
+ var $toggle = $(this);
+ var doc_width = $(document).width();
+ var $menu = $toggle.siblings('.oe_dropdown_menu');
+ $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
+ var state = $menu.is('.oe_opened');
+ setTimeout(function() {
+ // Do not alter propagation
+ $toggle.add($menu).toggleClass('oe_opened', !state);
+ if (!state) {
+ // Move $menu if outside window's edge
+ var offset = $menu.offset();
+ var menu_width = $menu.width();
+ var x = doc_width - offset.left - menu_width - 2;
+ if (x < 0) {
+ $menu.offset({ left: offset.left + x }).width(menu_width);
+ }
+ }
+ }, 0);
+ });
+ core.bus.on('click', this, function(ev) {
+ $('.tooltip').remove();
+ if (!$(ev.target).is('input[type=file]')) {
+ self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
+ }
+ });
+ core.bus.on('set_full_screen', this, function (full_screen) {
+ this.set_content_full_screen(full_screen);
+ });
+ },
+ on_logo_click: function(ev){
+ if (!this.has_uncommitted_changes()) {
+ return;
+ } else {
+ ev.preventDefault();
+ }
+ },
+ show_common: function() {
+ var self = this;
+ session.on('error', crash_manager, crash_manager.rpc_error);
+ self.notification = new Notification(this);
+ self.notification.appendTo(self.$('.openerp'));
+ self.loading = new Loading(self);
+ self.loading.appendTo(self.$('.openerp_webclient_container'));
+ self.action_manager = new ActionManager(self);
+ self.action_manager.replace(self.$('.oe_application'));
+
+ core.bus.on('display_notification_warning', this, function (title, message) {
+ self.notification.warn(title, message);
+ });
+ window.onerror = function (message, file, line) {
+ crash_manager.show_error({
+ type: _t("Client Error"),
+ message: message,
+ data: {debug: file + ':' + line}
+ });
+ };
+
+ },
+ toggle_bars: function(value) {
+ this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
+ },
+ has_uncommitted_changes: function() {
+ var $e = $.Event('clear_uncommitted_changes');
+ core.bus.trigger('clear_uncommitted_changes', $e);
+ return $e.isDefaultPrevented();
+ },
+ /**
+ Sets the first part of the title of the window, dedicated to the current action.
+ */
+ set_title: function(title) {
+ this.set_title_part("action", title);
+ },
+ /**
+ Sets an arbitrary part of the title of the window. Title parts are identified by strings. Each time
+ a title part is changed, all parts are gathered, ordered by alphabetical order and displayed in the
+ title of the window separated by '-'.
+ */
+ set_title_part: function(part, title) {
+ var tmp = _.clone(this.get("title_part"));
+ tmp[part] = title;
+ this.set("title_part", tmp);
+ },
+ _title_changed: function() {
+ var parts = _.sortBy(_.keys(this.get("title_part")), function(x) { return x; });
+ var tmp = "";
+ _.each(parts, function(part) {
+ var str = this.get("title_part")[part];
+ if (str) {
+ tmp = tmp ? tmp + " - " + str : str;
+ }
+ }, this);
+ document.title = tmp;
+ },
+ show_application: function() {
+ var self = this;
+ self.toggle_bars(true);
+
+ self.update_logo();
+
+ // Menu is rendered server-side thus we don't want the widget to create any dom
+ self.menu = new Menu(self);
+ self.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
+ self.menu.on('menu_click', this, this.on_menu_action);
+
+ // Create the user menu (rendered client-side)
+ self.user_menu = new UserMenu(self);
+ var user_menu_loaded = self.user_menu.appendTo(this.$el.parents().find('.oe_user_menu_placeholder'));
+ self.user_menu.on('user_logout', self, self.on_logout);
+ self.user_menu.do_update();
+
+ // Create the systray menu (rendered server-side)
+ self.systray_menu = new SystrayMenu(self);
+ self.systray_menu.setElement(this.$el.parents().find('.oe_systray'));
+ var systray_menu_loaded = self.systray_menu.start();
+
+ // Start the menu once both systray and user menus are rendered
+ // to prevent overflows while loading
+ $.when(systray_menu_loaded, user_menu_loaded).done(function() {
+ self.menu.start();
+ });
+
+ self.bind_hashchange();
+ self.set_title();
+ self.check_timezone();
+ if (self.client_options.action_post_login) {
+ self.action_manager.do_action(self.client_options.action_post_login);
+ delete(self.client_options.action_post_login);
+ }
+ },
+ update_logo: function() {
+ var company = session.company_id;
+ var img = session.url('/web/binary/company_logo' + '?db=' + session.db + (company ? '&company=' + company : ''));
+ this.$('.oe_logo img').attr('src', '').attr('src', img);
+ this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', session.uid === 1);
+ },
+ logo_edit: function(ev) {
+ var self = this;
+ ev.preventDefault();
+ self.alive(new Model("res.users").get_func("read")(session.uid, ["company_id"])).then(function(res) {
+ self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
+ result.res_id = res.company_id[0];
+ result.target = "new";
+ result.views = [[false, 'form']];
+ result.flags = {
+ action_buttons: true,
+ headless: true,
+ };
+ self.action_manager.do_action(result);
+ var form = self.action_manager.dialog_widget.views.form.controller;
+ form.on("on_button_cancel", self.action_manager, self.action_manager.dialog_stop);
+ form.on('record_saved', self, function() {
+ self.action_manager.dialog_stop();
+ self.update_logo();
+ });
+ });
+ });
+ return false;
+ },
+ check_timezone: function() {
+ var self = this;
+ return self.alive(new Model('res.users').call('read', [[session.uid], ['tz_offset']])).then(function(result) {
+ var user_offset = result[0]['tz_offset'];
+ var offset = -(new Date().getTimezoneOffset());
+ // _.str.sprintf()'s zero front padding is buggy with signed decimals, so doing it manually
+ var browser_offset = (offset < 0) ? "-" : "+";
+ browser_offset += _.str.sprintf("%02d", Math.abs(offset / 60));
+ browser_offset += _.str.sprintf("%02d", Math.abs(offset % 60));
+ if (browser_offset !== user_offset) {
+ var $icon = $(QWeb.render('WebClient.timezone_systray'));
+ $icon.on('click', function() {
+ var notification = self.do_warn(_t("Timezone Mismatch"), QWeb.render('WebClient.timezone_notification', {
+ user_timezone: session.user_context.tz || 'UTC',
+ user_offset: user_offset,
+ browser_offset: browser_offset,
+ }), true);
+ notification.element.find('.oe_webclient_timezone_notification').on('click', function() {
+ notification.close();
+ }).find('a').on('click', function() {
+ notification.close();
+ self.user_menu.on_menu_settings();
+ return false;
+ });
+ });
+ $icon.prependTo(window.$('.oe_systray'));
+ }
+ });
+ },
+ destroy_content: function() {
+ _.each(_.clone(this.getChildren()), function(el) {
+ el.destroy();
+ });
+ this.$el.children().remove();
+ },
+ do_reload: function() {
+ var self = this;
+ return this.session.session_reload().then(function () {
+ session.load_modules(true).then(
+ self.menu.proxy('do_reload')); });
+ },
+ do_notify: function() {
+ var n = this.notification;
+ return n.notify.apply(n, arguments);
+ },
+ do_warn: function() {
+ var n = this.notification;
+ return n.warn.apply(n, arguments);
+ },
+ on_logout: function() {
+ var self = this;
+ if (!this.has_uncommitted_changes()) {
+ self.action_manager.do_action('logout');
+ }
+ },
+ bind_hashchange: function() {
+ var self = this;
+ $(window).bind('hashchange', this.on_hashchange);
+
+ var state = $.bbq.getState(true);
+ if (_.isEmpty(state) || state.action == "login") {
+ self.menu.is_bound.done(function() {
+ new Model("res.users").call("read", [session.uid, ["action_id"]]).done(function(data) {
+ if(data.action_id) {
+ self.action_manager.do_action(data.action_id[0]);
+ self.menu.open_action(data.action_id[0]);
+ } else {
+ var first_menu_id = self.menu.$el.find("a:first").data("menu");
+ if(first_menu_id) {
+ self.menu.menu_click(first_menu_id);
+ } }
+ });
+ });
+ } else {
+ $(window).trigger('hashchange');
+ }
+ },
+ on_hashchange: function(event) {
+ var self = this;
+ var stringstate = event.getState(false);
+ if (!_.isEqual(this._current_state, stringstate)) {
+ var state = event.getState(true);
+ if(!state.action && state.menu_id) {
+ self.menu.is_bound.done(function() {
+ self.menu.menu_click(state.menu_id);
+ });
+ } else {
+ state._push_me = false; // no need to push state back...
+ this.action_manager.do_load_state(state, !!this._current_state);
+ }
+ }
+ this._current_state = stringstate;
+ },
+ do_push_state: function(state) {
+ this.set_title(state.title);
+ delete state.title;
+ var url = '#' + $.param(state);
+ this._current_state = $.deparam($.param(state), false); // stringify all values
+ $.bbq.pushState(url);
+ this.trigger('state_pushed', state);
+ },
+ on_menu_action: function(options) {
+ var self = this;
+ return this.menu_dm.add(this.rpc("/web/action/load", { action_id: options.action_id }))
+ .then(function (result) {
+ return self.action_mutex.exec(function() {
+ if (options.needaction) {
+ result.context = new data.CompoundContext(result.context, {
+ search_default_message_unread: true,
+ search_disable_custom_filters: true,
+ });
+ }
+ var completed = $.Deferred();
+ $.when(self.action_manager.do_action(result, {
+ clear_breadcrumbs: true,
+ action_menu_id: self.menu.current_menu,
+ })).fail(function() {
+ self.menu.open_menu(options.previous_menu_id);
+ }).always(function() {
+ completed.resolve();
+ });
+ setTimeout(function() {
+ completed.resolve();
+ }, 2000);
+ // We block the menu when clicking on an element until the action has correctly finished
+ // loading. If something crash, there is a 2 seconds timeout before it's unblocked.
+ return completed;
+ });
+ });
+ },
+ set_content_full_screen: function(fullscreen) {
+ $(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll');
+ this.$('.oe_webclient').toggleClass(
+ 'oe_content_full_screen', fullscreen);
+ },
+});
+
+return WebClient;
+
+});