summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/chrome
diff options
context:
space:
mode:
authorAlexandre Kühn <aku@odoo.com>2018-09-17 12:28:39 +0200
committerAlexandre Kühn <aku@odoo.com>2018-09-17 16:46:53 +0200
commit19eacf7d23c9413de4430a3422b5ed74b37ef242 (patch)
treefc96508d942cf2a31465545293f8856bb85838aa /addons/web/static/src/js/chrome
parent110d37a31d579d4a221e0a0abc275bd6920a5159 (diff)
[IMP] web: new navbar in community
The navbar in community now looks more like the navbar in enterprise. Here is a sum-up of the changes in community: - The navbar in community is now in blue, instead of black. - No more app list that almost fills the whole navbar: there is now an "Apps Menu" in the left-most side of the navbar. - No more sidebar for the menus inside an app: those menus are now located in the middle part of the navbar. The navbar in community does not support mobile (< 768px width), but it should still somewhat usable on most mobile devices. As for the website navbar, it has been unchanged and should still support mobile layout. Task-ID 1878649
Diffstat (limited to 'addons/web/static/src/js/chrome')
-rw-r--r--addons/web/static/src/js/chrome/apps_menu.js104
-rw-r--r--addons/web/static/src/js/chrome/menu.js300
-rw-r--r--addons/web/static/src/js/chrome/web_client.js353
3 files changed, 491 insertions, 266 deletions
diff --git a/addons/web/static/src/js/chrome/apps_menu.js b/addons/web/static/src/js/chrome/apps_menu.js
new file mode 100644
index 00000000000..bdc330b0e29
--- /dev/null
+++ b/addons/web/static/src/js/chrome/apps_menu.js
@@ -0,0 +1,104 @@
+odoo.define('web.AppsMenu', function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+var AppsMenu = Widget.extend({
+ template: 'AppsMenu',
+ events: {
+ 'click .o_app': '_onAppsMenuItemClicked',
+ },
+ /**
+ * @override
+ * @param {web.Widget} parent
+ * @param {Object} menuData
+ * @param {Object[]} menuData.children
+ */
+ init: function (parent, menuData) {
+ this._super.apply(this, arguments);
+ this._activeApp = undefined;
+ this._apps = _.map(menuData.children, function (appMenuData) {
+ return {
+ actionID: parseInt(appMenuData.action.split(',')[1]),
+ menuID: appMenuData.id,
+ name: appMenuData.name,
+ xmlID: appMenuData.xmlid,
+ };
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {Object}
+ */
+ getActiveApp: function () {
+ return this._activeApp || {};
+ },
+ /**
+ * @returns {Object[]}
+ */
+ getApps: function () {
+ return this._apps;
+ },
+ /**
+ * Open the given app
+ */
+ openApp: function () {},
+ /**
+ * Open the first app in the list of apps
+ */
+ openFirstApp: function () {
+ var firstApp = this._apps[0];
+ this._openApp(firstApp);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Object} app
+ */
+ _openApp: function (app) {
+ this._setActiveApp(app);
+ this.trigger_up('app_clicked', {
+ action_id: app.actionID,
+ menu_id: app.menuID,
+ });
+ },
+ /**
+ * @private
+ * @param {Object} app
+ */
+ _setActiveApp: function (app) {
+ this._activeApp = app;
+ this.renderElement();
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when clicking on an item in the apps menu.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onAppsMenuItemClicked: function (ev) {
+ var $target = $(ev.currentTarget);
+ var actionID = $target.data('action-id');
+ var menuID = $target.data('menu-id');
+ var app = _.findWhere(this._apps, { actionID: actionID, menuID: menuID });
+ this._openApp(app);
+ },
+
+});
+
+return AppsMenu;
+
+});
diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js
index c96d4b17f8c..7f60e2ea44f 100644
--- a/addons/web/static/src/js/chrome/menu.js
+++ b/addons/web/static/src/js/chrome/menu.js
@@ -1,179 +1,221 @@
odoo.define('web.Menu', function (require) {
"use strict";
+var AppsMenu = require('web.AppsMenu');
+var config = require('web.config');
var core = require('web.core');
var dom = require('web.dom');
-var session = require('web.session');
+var SystrayMenu = require('web.SystrayMenu');
+var UserMenu = require('web.UserMenu');
var Widget = require('web.Widget');
+UserMenu.prototype.sequence = 0; // force UserMenu to be the right-most item in the systray
+SystrayMenu.Items.push(UserMenu);
+
+var QWeb = core.qweb;
+
var Menu = Widget.extend({
- init: function() {
- this._super.apply(this, arguments);
- this.is_bound = $.Deferred();
- this.data = {data:{children:[]}};
- core.bus.on('change_menu_section', this, this.on_change_top_menu);
- },
- start: function() {
- this._super.apply(this, arguments);
- return this.bind_menu();
+ template: 'Menu',
+ menusTemplate: 'Menu.sections',
+ events: {
+ 'mouseover .o_menu_sections > li:not(.show)': '_onMouseOverMenu',
+ 'click .o_menu_brand': '_onAppNameClicked',
},
- do_reload: function() {
- var self = this;
- self.bind_menu();
- },
- bind_menu: function() {
+
+ init: function (parent, menu_data) {
var self = this;
- this.$secondary_menus = this.$el.parents().find('.o_sub_menu');
- this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
- this.$el.on('click', 'a[data-menu]', function (event) {
- event.preventDefault();
- var menu_id = $(event.currentTarget).data('menu');
- core.bus.trigger('change_menu_section', menu_id);
- });
+ this._super.apply(this, arguments);
- // Hide second level submenus
- this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').addClass('o_hidden');
- if (self.current_menu) {
- self.open_menu(self.current_menu);
- }
- this.trigger('menu_bound');
+ this.$menu_sections = {};
+ this.menu_data = menu_data;
- dom.initAutoMoreMenu(this.$el);
+ // Prepare navbar's menus
+ var $menu_sections = $(QWeb.render(this.menusTemplate, {
+ menu_data: this.menu_data,
+ }));
+ $menu_sections.filter('section').each(function () {
+ self.$menu_sections[parseInt(this.className, 10)] = $(this).children('li');
+ });
- this.is_bound.resolve();
+ // Bus event
+ core.bus.on('change_menu_section', this, this._onChangeMenuSection);
},
- /**
- * Opens a given menu by id, as if a user had browsed to that menu by hand
- * except does not trigger any event on the way
- *
- * @param {Number} id database id of the terminal menu to select
- */
- open_menu: function (id) {
- this.current_menu = id;
- session.active_id = id;
- var $clicked_menu, $sub_menu, $main_menu;
- $clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']');
- this.trigger('open_menu', id, $clicked_menu);
-
- if (this.$secondary_menus.has($clicked_menu).length) {
- $sub_menu = $clicked_menu.parents('.oe_secondary_menu');
- $main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
- } else {
- $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
- $main_menu = $clicked_menu;
+ start: function () {
+ var self = this;
+
+ this.$menu_apps = this.$('.o_menu_apps');
+ this.$menu_brand_placeholder = this.$('.o_menu_brand');
+ this.$section_placeholder = this.$('.o_menu_sections');
+
+ // Navbar's menus event handlers
+ var on_secondary_menu_click = function (ev) {
+ ev.preventDefault();
+ var menu_id = $(ev.currentTarget).data('menu');
+ var action_id = $(ev.currentTarget).data('action-id');
+ self._on_secondary_menu_click(menu_id, action_id);
+ };
+ var menu_ids = _.keys(this.$menu_sections);
+ var primary_menu_id, $section;
+ for (var i = 0; i < menu_ids.length; i++) {
+ primary_menu_id = menu_ids[i];
+ $section = this.$menu_sections[primary_menu_id];
+ $section.on('click', 'a[data-menu]', self, on_secondary_menu_click.bind(this));
}
- this.current_primary_menu = $main_menu.data('menu');
+ // Apps Menu
+ this._appsMenu = new AppsMenu(self, this.menu_data);
+ this._appsMenu.appendTo(this.$menu_apps);
- // Activate current main menu
- this.$el.find('.active').removeClass('active');
- $main_menu.parent().addClass('active');
+ // Systray Menu
+ this.systray_menu = new SystrayMenu(this);
+ this.systray_menu.attachTo(this.$('.o_menu_systray'));
- // Show current sub menu
- this.$secondary_menus.find('.oe_secondary_menu').hide();
- $sub_menu.show();
+ dom.initAutoMoreMenu(this.$section_placeholder, {
+ maxWidth: function () {
+ return self.$el.width() - (self.$menu_apps.outerWidth(true) + self.$menu_brand_placeholder.outerWidth(true) + self.systray_menu.$el.outerWidth(true));
+ },
+ sizeClass: 'SM',
+ });
- // Hide/Show the leftbar menu depending of the presence of sub-items
- this.$secondary_menus.toggleClass('o_hidden', !$sub_menu.children().length);
+ return this._super.apply(this, arguments);
+ },
+ _trigger_menu_clicked: function (menu_id, action_id) {
+ this.trigger_up('menu_clicked', {
+ id: menu_id,
+ action_id: action_id,
+ previous_menu_id: this.current_secondary_menu || this.current_primary_menu,
+ });
+ },
+ _on_secondary_menu_click: function (menu_id, action_id) {
+ var self = this;
- // Activate current menu item and show parents
- this.$secondary_menus.find('.active').removeClass('active');
- if ($main_menu !== $clicked_menu) {
- $clicked_menu.parents().removeClass('o_hidden');
- if ($clicked_menu.is('.oe_menu_toggler')) {
- $clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggleClass('o_hidden');
- } else {
- $clicked_menu.parent().addClass('active');
- }
+ // It is still possible that we don't have an action_id (for example, menu toggler)
+ if (action_id) {
+ self._trigger_menu_clicked(menu_id, action_id);
+ this.current_secondary_menu = menu_id;
}
- // add a tooltip to cropped menu items
- this.$secondary_menus.find('.oe_secondary_submenu li a span').each(function() {
- $(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} : 'dispose');
- });
},
/**
- * Call open_menu on a menu_item that matches the action_id
- *
- * If `menuID` is a match on this action, open this menu_item.
- * Otherwise open the first menu_item that matches the action_id.
- *
- * @param {Number} id the action_id to match
- * @param {Number} [menuID] a menu ID that may match with provided action
+ * Helpers used by web_client in order to restore the state from
+ * an url (by restore, read re-synchronize menu and action manager)
*/
- open_action: function (id, menuID) {
- var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]');
- if (!(menuID && $menu.filter("[data-menu='" + menuID + "']").length)) {
- // menuID doesn't match action, so pick first menu_item
- menuID = $menu.data('menu');
+ action_id_to_primary_menu_id: function (action_id) {
+ var primary_menu_id, found;
+ for (var i = 0; i < this.menu_data.children.length && !primary_menu_id; i++) {
+ found = this._action_id_in_subtree(this.menu_data.children[i], action_id);
+ if (found) {
+ primary_menu_id = this.menu_data.children[i].id;
+ }
}
- if (menuID) {
- this.open_menu(menuID);
+ return primary_menu_id;
+ },
+ _action_id_in_subtree: function (root, action_id) {
+ // action_id can be a string or an integer
+ if (root.action && root.action.split(',')[1] === String(action_id)) {
+ return true;
+ }
+ var found;
+ for (var i = 0; i < root.children.length && !found; i++) {
+ found = this._action_id_in_subtree(root.children[i], action_id);
}
+ return found;
},
- /**
- * Process a click on a menu item
- *
- * @param {Number} id the menu_id
- */
- menu_click: function(id) {
- if (!id) { return; }
+ menu_id_to_action_id: function (menu_id, root) {
+ if (!root) {
+ root = $.extend(true, {}, this.menu_data);
+ }
- // find back the menuitem in dom to get the action
- var $item = this.$el.find('a[data-menu=' + id + ']');
- if (!$item.length) {
- $item = this.$secondary_menus.find('a[data-menu=' + id + ']');
+ if (root.id === menu_id) {
+ return root.action.split(',')[1] ;
}
- var action_id = $item.data('action-id');
- // If first level menu doesnt have action trigger first leaf
- if (!action_id) {
- if(this.$el.has($item).length) {
- var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']');
- var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]');
- if($items.length) {
- action_id = $items.data('action-id');
- id = $items.data('menu');
- }
+ for (var i = 0; i < root.children.length; i++) {
+ var action_id = this.menu_id_to_action_id(menu_id, root.children[i]);
+ if (action_id !== undefined) {
+ return action_id;
}
}
- if (action_id) {
- this.trigger('menu_click', {
- action_id: action_id,
- id: id,
- previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu)
- }, $item);
- } else {
- console.log('Menu no action found web test 04 will fail');
- }
- this.open_menu(id);
+ return undefined;
},
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
/**
- * Change the current top menu
+ * Returns the id of the current primary (first level) menu.
*
- * @param {int} [menu_id] the top menu id
+ * @returns {integer}
*/
- on_change_top_menu: function(menu_id) {
- var self = this;
- this.menu_click(menu_id);
+ getCurrentPrimaryMenu: function () {
+ return this.current_primary_menu;
},
- on_menu_click: function(ev) {
- ev.preventDefault();
- this.menu_click($(ev.currentTarget).data('menu'));
+ /**
+ * Open the first app
+ */
+ openFirstApp: function () {
+ this._appsMenu.openFirstApp();
},
//--------------------------------------------------------------------------
- // Public
+ // Handlers
//--------------------------------------------------------------------------
/**
- * Returns the id of the current primary (first level) menu.
+ * When clicking on app name, opens the first action of the app
*
- * @returns {integer}
+ * @private
+ * @param {MouseEvent} ev
*/
- getCurrentPrimaryMenu: function () {
- return this.current_primary_menu;
+ _onAppNameClicked: function (ev) {
+ var actionID = parseInt(this.menu_id_to_action_id(this.current_primary_menu));
+ this._trigger_menu_clicked(this.current_primary_menu, actionID);
+ },
+ /**
+ * @private
+ * @param {integer} primaryMenuID
+ */
+ _onChangeMenuSection: function (primaryMenuID) {
+ if (!this.$menu_sections[primaryMenuID]) {
+ return; // unknown menu_id
+ }
+
+ if (this.current_primary_menu === primaryMenuID) {
+ return; // already in that menu
+ }
+
+ if (this.current_primary_menu) {
+ this.$menu_sections[this.current_primary_menu].detach();
+ }
+
+ // Get back the application name
+ for (var i = 0; i < this.menu_data.children.length; i++) {
+ if (this.menu_data.children[i].id === primaryMenuID) {
+ this.$menu_brand_placeholder.text(this.menu_data.children[i].name);
+ break;
+ }
+ }
+
+ this.$menu_sections[primaryMenuID].appendTo(this.$section_placeholder);
+ this.current_primary_menu = primaryMenuID;
+
+ core.bus.trigger('resize');
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onMouseOverMenu: function (ev) {
+ if (config.device.isMobile) {
+ return;
+ }
+ var $target = $(ev.currentTarget);
+ var $opened = $target.siblings('.show');
+ if ($opened.length) {
+ $target.find('[data-toggle="dropdown"]').dropdown('toggle');
+ }
},
});
return Menu;
+
});
diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js
index ca777d470ae..8b725423d0f 100644
--- a/addons/web/static/src/js/chrome/web_client.js
+++ b/addons/web/static/src/js/chrome/web_client.js
@@ -6,149 +6,164 @@ var config = require('web.config');
var core = require('web.core');
var data_manager = require('web.data_manager');
var dom = require('web.dom');
-var framework = require('web.framework');
var Menu = require('web.Menu');
var session = require('web.session');
-var SystrayMenu = require('web.SystrayMenu');
-var UserMenu = require('web.UserMenu');
return AbstractWebClient.extend({
- events: _.extend({}, AbstractWebClient.prototype.events, {
- 'click .oe_logo_edit_admin': 'logo_edit',
- 'click .oe_logo img': function(ev) {
- ev.preventDefault();
- return this.clear_uncommitted_changes().then(function() {
- framework.redirect("/web" + (config.debug ? "?debug" : ""));
- });
- },
+ custom_events: _.extend({}, AbstractWebClient.prototype.custom_events, {
+ app_clicked: 'on_app_clicked',
+ menu_clicked: 'on_menu_clicked',
}),
- show_application: function() {
- var self = this;
-
- this.toggle_bars(true);
- this.set_title();
- this.update_logo();
-
- // Menu is rendered server-side thus we don't want the widget to create any dom
- this.menu = new Menu(this);
- this.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
- this.menu.on('menu_click', this, this.on_menu_action);
-
- // Create the user menu (rendered client-side)
- this.user_menu = new UserMenu(this);
- var $user_menu_placeholder = $('body').find('.oe_user_menu_placeholder').show();
- var user_menu_loaded = this.user_menu.appendTo($user_menu_placeholder);
-
- // Create the systray menu (rendered server-side)
- this.systray_menu = new SystrayMenu(this);
- this.systray_menu.setElement(this.$el.parents().find('.oe_systray'));
- var systray_menu_loaded = this.systray_menu.start();
-
- // Start the menu once both systray and user menus are rendered
- // to prevent overflows while loading
- return $.when(systray_menu_loaded, user_menu_loaded).then(function() {
- self.bind_hashchange();
- self.menu.$el.removeClass('d-none');
- self.menu.start();
+ start: function () {
+ core.bus.on('change_menu_section', this, function (menuID) {
+ this.do_push_state(_.extend($.bbq.getState(), {
+ menu_id: menuID,
+ }));
});
+ return this._super.apply(this, arguments);
},
- toggle_bars: function(value) {
- this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
- },
- update_logo: function(reload) {
- var company = session.company_id;
- var img = session.url('/web/binary/company_logo' + '?db=' + session.db + (company ? '&company=' + company : ''));
- this.$('.o_sub_menu_logo img').attr('src', '').attr('src', img + (reload ? "&t=" + Date.now() : ''));
- this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', session.is_superuser);
- },
- logo_edit: function(ev) {
+ bind_events: function () {
var self = this;
- ev.preventDefault();
- this._rpc({
- model: 'res.users',
- method: 'read',
- args: [[session.uid], ['company_id']],
+ this._super.apply(this, arguments);
+
+ /*
+ Small patch to allow having a link with a href towards an anchor. Since odoo use hashtag
+ to represent the current state of the view, we can't easily distinguish between a link
+ towards an anchor and a link towards anoter view/state. If we want to navigate towards an
+ anchor, we must not change the hash of the url otherwise we will be redirected to the app
+ switcher instead.
+ To check if we have an anchor, first check if we have an href attributes starting with #.
+ Try to find a element in the DOM using JQuery selector.
+ If we have a match, it means that it is probably a link to an anchor, so we jump to that anchor.
+ */
+ this.$el.on('click', 'a', function (ev) {
+ var disable_anchor = ev.target.attributes.disable_anchor;
+ if (disable_anchor && disable_anchor.value === "true") {
+ return;
+ }
+
+ var href = ev.target.attributes.href;
+ if (href) {
+ if (href.value[0] === '#' && href.value.length > 1) {
+ if (self.$("[id='"+href.value.substr(1)+"']").length) {
+ ev.preventDefault();
+ self.trigger_up('scrollTo', {'selector': href.value});
+ }
+ }
+ }
+ });
+ },
+ load_menus: function () {
+ return this._rpc({
+ model: 'ir.ui.menu',
+ method: 'load_menus',
+ args: [config.debug],
+ context: session.user_context,
})
- .then(function(data) {
- self._rpc({
- route: '/web/action/load',
- params: { action_id: 'base.action_res_company_form' },
- })
- .done(function(result) {
- result.res_id = data[0].company_id[0];
- result.target = "new";
- result.views = [[false, 'form']];
- result.flags = {
- action_buttons: true,
- headless: true,
- };
- self.action_manager.doAction(result, {
- on_close: self.update_logo.bind(self, true),
- });
- });
+ .then(function (menuData) {
+ // Compute action_id if not defined on a top menu item
+ for (var i = 0; i < menuData.children.length; i++) {
+ var child = menuData.children[i];
+ if (child.action === false) {
+ while (child.children && child.children.length) {
+ child = child.children[0];
+ if (child.action) {
+ menuData.children[i].action = child.action;
+ break;
+ }
+ }
+ }
+ }
+ return menuData;
});
- return false;
},
- bind_hashchange: function() {
+ show_application: function () {
var self = this;
- $(window).bind('hashchange', this.on_hashchange);
- var didHashChanged = false;
- $(window).one('hashchange', function () {
- didHashChanged = true;
- });
+ this.set_title();
+
+ return this.instanciate_menu_widgets().then(function () {
+ $(window).bind('hashchange', self.on_hashchange);
- var state = $.bbq.getState(true);
- if (_.isEmpty(state) || state.action === "login") {
- self.menu.is_bound.done(function() {
- self._rpc({
+ // If the url's state is empty, we execute the user's home action if there is one (we
+ // show the first app if not)
+ if (_.isEmpty($.bbq.getState(true))) {
+ return self._rpc({
model: 'res.users',
method: 'read',
- args: [[session.uid], ['action_id']],
+ args: [session.uid, ["action_id"]],
})
- .done(function(result) {
- var amDef = self.action_manager.dp.current_def;
- if (didHashChanged || (amDef && amDef.state() === 'pending')) {
- return;
- }
+ .then(function (result) {
var data = result[0];
- if(data.action_id) {
- self.action_manager.doAction(data.action_id[0]);
- self.menu.open_action(data.action_id[0]);
+ if (data.action_id) {
+ return self.do_action(data.action_id[0]).then(function () {
+ self.menu.change_menu_section(self.menu.action_id_to_primary_menu_id(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);
- }
+ self.menu.openFirstApp();
}
});
- });
- } else {
- $(window).trigger('hashchange');
- }
+ } else {
+ return self.on_hashchange();
+ }
+ });
},
- on_hashchange: function(event) {
+
+ instanciate_menu_widgets: function () {
+ var self = this;
+ var defs = [];
+ return this.load_menus().then(function (menuData) {
+ self.menu_data = menuData;
+
+ // Here, we instanciate every menu widgets and we immediately append them into dummy
+ // document fragments, so that their `start` method are executed before inserting them
+ // into the DOM.
+ if (self.menu) {
+ self.menu.destroy();
+ }
+ self.menu = new Menu(self, menuData);
+ defs.push(self.menu.prependTo(self.$el));
+ return $.when.apply($, defs);
+ });
+ },
+
+ // --------------------------------------------------------------
+ // URL state handling
+ // --------------------------------------------------------------
+ on_hashchange: function (event) {
if (this._ignore_hashchange) {
this._ignore_hashchange = false;
- return;
+ return $.when();
}
var self = this;
- this.clear_uncommitted_changes().then(function () {
- var stringstate = event.getState(false);
+ return this.clear_uncommitted_changes().then(function () {
+ var stringstate = $.bbq.getState(false);
if (!_.isEqual(self._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 {
- self.action_manager.loadState(state, !!self._current_state).then(function () {
- var action = self.action_manager.getCurrentAction();
- if (action) {
- self.menu.open_action(action.id, state.menu_id);
+ var state = $.bbq.getState(true);
+ if (state.action || (state.model && (state.view_type || state.id))) {
+ return self.action_manager.loadState(state, !!self._current_state).then(function () {
+ if (state.menu_id) {
+ if (state.menu_id !== self.menu.current_primary_menu) {
+ core.bus.trigger('change_menu_section', state.menu_id);
+ }
+ } else {
+ var action = self.action_manager.getCurrentAction();
+ if (action) {
+ var menu_id = self.menu.action_id_to_primary_menu_id(action.id);
+ if (menu_id) {
+ core.bus.trigger('change_menu_section', menu_id);
+ }
+ }
}
});
+ } else if (state.menu_id) {
+ var action_id = self.menu.menu_id_to_action_id(state.menu_id);
+ return self.do_action(action_id, {clear_breadcrumbs: true}).then(function () {
+ core.bus.trigger('change_menu_section', state.menu_id);
+ });
+ } else {
+ self.menu.openFirstApp();
}
}
self._current_state = stringstate;
@@ -159,17 +174,88 @@ return AbstractWebClient.extend({
}
});
},
- on_menu_action: function(options) {
- this.action_manager.doAction(options.action_id, {
- clear_breadcrumbs: true,
- action_menu_id: options.id,
- });
+
+ // --------------------------------------------------------------
+ // Menu handling
+ // --------------------------------------------------------------
+ on_app_clicked: function (ev) {
+ var self = this;
+ return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
+ .then(function (result) {
+ return self.action_mutex.exec(function () {
+ var completed = $.Deferred();
+ var options = _.extend({}, ev.data.options, {
+ clear_breadcrumbs: true,
+ action_menu_id: ev.data.menu_id,
+ });
+ $.when(self._openMenu(result, options)).fail(function () {
+ completed.resolve();
+ }).done(function () {
+ self._on_app_clicked_done(ev)
+ .then(completed.resolve.bind(completed))
+ .fail(completed.reject.bind(completed));
+ });
+ setTimeout(function () {
+ completed.resolve();
+ }, 2000);
+ return completed;
+ });
+ });
},
- toggle_fullscreen: function(fullscreen) {
- this._super(fullscreen);
- if (!fullscreen) {
- core.bus.trigger('resize');
- }
+ _on_app_clicked_done: function (ev) {
+ core.bus.trigger('change_menu_section', ev.data.menu_id);
+ return $.Deferred().resolve();
+ },
+ on_menu_clicked: function (ev) {
+ var self = this;
+ return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
+ .then(function (result) {
+ return self.action_mutex.exec(function () {
+ var completed = $.Deferred();
+ $.when(self._openMenu(result, {
+ clear_breadcrumbs: true,
+ })).always(function () {
+ completed.resolve();
+ });
+
+ setTimeout(function () {
+ completed.resolve();
+ }, 2000);
+
+ return completed;
+ });
+ }).always(function () {
+ self.$el.removeClass('o_mobile_menu_opened');
+ });
+ },
+ /**
+ * Open the action linked to a menu.
+ * This function is mostly used to allow override in other modules.
+ *
+ * @private
+ * @param {Object} action
+ * @param {Object} options
+ * @returns {Deferred}
+ */
+ _openMenu: function (action, options) {
+ return this.do_action(action, options);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Returns the left and top scroll positions of the main scrolling area
+ * (i.e. the action manager in desktop).
+ *
+ * @returns {Object} with keys left and top
+ */
+ getScrollPosition: function () {
+ return {
+ left: this.action_manager.el.scrollLeft,
+ top: this.action_manager.el.scrollTop,
+ };
},
//--------------------------------------------------------------------------
@@ -178,29 +264,22 @@ return AbstractWebClient.extend({
/**
* @override
+ * @private
*/
_onGetScrollPosition: function (ev) {
- ev.data.callback({
- left: this.action_manager.el.scrollLeft,
- top: this.action_manager.el.scrollTop,
- });
+ ev.data.callback(this.getScrollPosition());
},
/**
* @override
+ * @private
*/
_onScrollTo: function (ev) {
- var offset;
- if (ev.data.selector) {
+ var offset = {top: ev.data.top, left: ev.data.left || 0};
+ if (!offset.top) {
offset = dom.getPosition(document.querySelector(ev.data.selector));
- // substract the position of the ActionManager as it is the
- // scrolling element
- var actionManagerOffset = dom.getPosition(this.action_manager.el);
- offset.left -= actionManagerOffset.left;
- offset.top -= actionManagerOffset.top;
- } else {
- offset = {top: ev.data.top || 0, left: ev.data.left || 0};
+ // Substract the position of the action_manager as it is the scrolling part
+ offset.top -= dom.getPosition(this.action_manager.el).top;
}
-
this.action_manager.el.scrollTop = offset.top;
this.action_manager.el.scrollLeft = offset.left;
},