odoo.define('web.AbstractWebClient', function (require) { "use strict"; /** * AbstractWebClient * * This class defines a simple, basic web client. It is mostly functional. * The WebClient is in some way the most important class for the web framework: * - this is the class that instantiate everything else, * - it is the top of the component tree, * - it coordinates many events bubbling up */ var ActionManager = require('web.ActionManager'); var concurrency = require('web.concurrency'); var core = require('web.core'); var config = require('web.config'); var crash_manager = require('web.crash_manager'); var data_manager = require('web.data_manager'); var Dialog = require('web.Dialog'); var dom = require('web.dom'); var KeyboardNavigationMixin = require('web.KeyboardNavigationMixin'); var Loading = require('web.Loading'); var RainbowMan = require('web.RainbowMan'); var ServiceProviderMixin = require('web.ServiceProviderMixin'); var session = require('web.session'); var Widget = require('web.Widget'); var _t = core._t; var qweb = core.qweb; var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMixin, { dependencies: ['notification'], events: _.extend({}, KeyboardNavigationMixin.events, { 'click .o_search_options .dropdown-menu': '_onClickDropDownMenu', }), custom_events: { clear_uncommitted_changes: function (e) { this.clear_uncommitted_changes().then(e.data.callback); }, toggle_fullscreen: function (event) { this.toggle_fullscreen(event.data.fullscreen); }, current_action_updated: function (ev) { this.current_action_updated(ev.data.action, ev.data.controller); }, // GENERIC SERVICES // the next events are dedicated to generic services required by // downstream widgets. Mainly side effects, such as rpcs, notifications // or cache. warning: '_onDisplayWarning', load_action: '_onLoadAction', load_views: function (event) { var params = { model: event.data.modelName, context: event.data.context, views_descr: event.data.views, }; return data_manager .load_views(params, event.data.options || {}) .then(event.data.on_success); }, load_filters: function (event) { return data_manager .load_filters(event.data) .then(event.data.on_success); }, create_filter: '_onCreateFilter', delete_filter: '_onDeleteFilter', push_state: '_onPushState', show_effect: '_onShowEffect', // session get_session: function (event) { if (event.data.callback) { event.data.callback(session); } }, do_action: function (event) { this.do_action(event.data.action, event.data.options || {}).then(function (result) { if (event.data.on_success) { event.data.on_success(result); } }).fail(function (result) { if (event.data.on_fail) { event.data.on_fail(result); } }); }, getScrollPosition: '_onGetScrollPosition', scrollTo: '_onScrollTo', set_title_part: '_onSetTitlePart', }, init: function (parent) { // a flag to determine that odoo is fully loaded odoo.isReady = false; this.client_options = {}; this._super(parent); ServiceProviderMixin.init.call(this); KeyboardNavigationMixin.init.call(this); this.origin = undefined; this._current_state = null; this.menu_dm = new concurrency.DropMisordered(); this.action_mutex = new concurrency.Mutex(); this.set('title_part', {"zopenerp": "Odoo"}); }, start: function () { var self = this; // we add the o_touch_device css class to allow CSS to target touch // devices. This is only for styling purpose, if you need javascript // specific behaviour for touch device, just use the config object // exported by web.config this.$el.toggleClass('o_touch_device', config.device.touch); this.on("change:title_part", this, this._title_changed); this._title_changed(); return session.is_bound .then(function () { self.$el.toggleClass('o_rtl', _t.database.parameters.direction === "rtl"); self.bind_events(); return $.when( self.set_action_manager(), self.set_loading() ); }).then(function () { if (session.session_is_valid()) { return self.show_application(); } else { // database manager needs the webclient to keep going even // though it has no valid session return $.when(); } }).then(function () { // Listen to 'scroll' event and propagate it on main bus self.action_manager.$el.on('scroll', core.bus.trigger.bind(core.bus, 'scroll')); core.bus.trigger('web_client_ready'); odoo.isReady = true; if (session.uid === 1) { self.$el.addClass('o_is_superuser'); } }); }, 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); }); window.addEventListener('blur', function (e) { self._hideAccessKeyOverlay(); }); core.bus.on('click', this, function (ev) { $('.tooltip').remove(); if (!$(ev.target).is('input[type=file]')) { $(this.el.getElementsByClassName('oe_dropdown_menu oe_opened')).removeClass('oe_opened'); $(this.el.getElementsByClassName('oe_dropdown_toggle oe_opened')).removeClass('oe_opened'); } this._hideAccessKeyOverlay(); }); core.bus.on('connection_lost', this, this._onConnectionLost); core.bus.on('connection_restored', this, this._onConnectionRestored); // crash manager integration core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error); window.onerror = function (message, file, line, col, error) { // Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror. // The browser will just give you a 'Script error.' as message and nothing else for security issue. // To enable onerror to work properly with CORS file, you should: // 1. add crossorigin="anonymous" to your