summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/web_client.js
diff options
context:
space:
mode:
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;
+
+});