summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/menu/navbar.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website/static/src/js/menu/navbar.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website/static/src/js/menu/navbar.js')
-rw-r--r--addons/website/static/src/js/menu/navbar.js292
1 files changed, 292 insertions, 0 deletions
diff --git a/addons/website/static/src/js/menu/navbar.js b/addons/website/static/src/js/menu/navbar.js
new file mode 100644
index 00000000..937392f8
--- /dev/null
+++ b/addons/website/static/src/js/menu/navbar.js
@@ -0,0 +1,292 @@
+odoo.define('website.navbar', function (require) {
+'use strict';
+
+var core = require('web.core');
+var dom = require('web.dom');
+var publicWidget = require('web.public.widget');
+var concurrency = require('web.concurrency');
+var Widget = require('web.Widget');
+var websiteRootData = require('website.root');
+
+var websiteNavbarRegistry = new publicWidget.RootWidgetRegistry();
+
+var WebsiteNavbar = publicWidget.RootWidget.extend({
+ xmlDependencies: ['/website/static/src/xml/website.xml'],
+ events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, {
+ 'click [data-action]': '_onActionMenuClick',
+ 'mouseover > ul > li.dropdown:not(.show)': '_onMenuHovered',
+ 'click .o_mobile_menu_toggle': '_onMobileMenuToggleClick',
+ 'mouseenter #oe_applications:not(:has(.dropdown-item))': '_onOeApplicationsHovered',
+ 'show.bs.dropdown #oe_applications:not(:has(.dropdown-item))': '_onOeApplicationsShow',
+ }),
+ custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, {
+ 'action_demand': '_onActionDemand',
+ 'edit_mode': '_onEditMode',
+ 'readonly_mode': '_onReadonlyMode',
+ 'ready_to_save': '_onSave',
+ }),
+
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ var self = this;
+ var initPromise = new Promise(function (resolve) {
+ self.resolveInit = resolve;
+ });
+ this._widgetDefs = [initPromise];
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+ dom.initAutoMoreMenu(this.$('ul.o_menu_sections'), {
+ maxWidth: function () {
+ // The navbar contains different elements in community and
+ // enterprise, so we check for both of them here only
+ return self.$el.width()
+ - (self.$('.o_menu_systray').outerWidth(true) || 0)
+ - (self.$('ul#oe_applications').outerWidth(true) || 0)
+ - (self.$('.o_menu_toggle').outerWidth(true) || 0)
+ - (self.$('.o_menu_brand').outerWidth(true) || 0);
+ },
+ });
+ return this._super.apply(this, arguments).then(function () {
+ self.resolveInit();
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _attachComponent: function () {
+ var def = this._super.apply(this, arguments);
+ this._widgetDefs.push(def);
+ return def;
+ },
+ /**
+ * As the WebsiteNavbar instance is designed to be unique, the associated
+ * registry has been instantiated outside of the class and is simply
+ * returned here.
+ *
+ * @override
+ */
+ _getRegistry: function () {
+ return websiteNavbarRegistry;
+ },
+ /**
+ * Searches for the automatic widget {@see RootWidget} which can handle that
+ * action.
+ *
+ * @private
+ * @param {string} actionName
+ * @param {Array} params
+ * @returns {Promise}
+ */
+ _handleAction: function (actionName, params, _i) {
+ var self = this;
+ return this._whenReadyForActions().then(function () {
+ var defs = [];
+ _.each(self._widgets, function (w) {
+ if (!w.handleAction) {
+ return;
+ }
+
+ var def = w.handleAction(actionName, params);
+ if (def !== null) {
+ defs.push(def);
+ }
+ });
+ if (!defs.length) {
+ // Handle the case where all action-capable components are not
+ // instantiated yet (rare) -> retry some times to eventually abort
+ if (_i > 50) {
+ console.warn(_.str.sprintf("Action '%s' was not able to be handled.", actionName));
+ return Promise.reject();
+ }
+ return concurrency.delay(100).then(function () {
+ return self._handleAction(actionName, params, (_i || 0) + 1);
+ });
+ }
+ return Promise.all(defs).then(function (values) {
+ if (values.length === 1) {
+ return values[0];
+ }
+ return values;
+ });
+ });
+ },
+ /**
+ * @private
+ * @returns {Promise}
+ */
+ async _loadAppMenus() {
+ if (!this._loadAppMenusProm) {
+ this._loadAppMenusProm = this._rpc({
+ model: 'ir.ui.menu',
+ method: 'load_menus_root',
+ args: [],
+ });
+ const result = await this._loadAppMenusProm;
+ const menus = core.qweb.render('website.oe_applications_menu', {
+ 'menu_data': result,
+ });
+ this.$('#oe_applications .dropdown-menu').html(menus);
+ }
+ return this._loadAppMenusProm;
+ },
+ /**
+ * @private
+ */
+ _whenReadyForActions: function () {
+ return Promise.all(this._widgetDefs);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when the backend applications menu is hovered -> fetch the
+ * available menus and insert it in DOM.
+ *
+ * @private
+ */
+ _onOeApplicationsHovered: function () {
+ this._loadAppMenus();
+ },
+ /**
+ * Called when the backend applications menu is opening -> fetch the
+ * available menus and insert it in DOM. Needed on top of hovering as the
+ * dropdown could be opened via keyboard (or the user could just already
+ * be over the dropdown when the JS is fully loaded).
+ *
+ * @private
+ */
+ _onOeApplicationsShow: function () {
+ this._loadAppMenus();
+ },
+ /**
+ * Called when an action menu is clicked -> searches for the automatic
+ * widget {@see RootWidget} which can handle that action.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onActionMenuClick: function (ev) {
+ const restore = dom.addButtonLoadingEffect(ev.currentTarget);
+ this._handleAction($(ev.currentTarget).data('action')).then(restore).guardedCatch(restore);
+ },
+ /**
+ * Called when an action is asked to be executed from a child widget ->
+ * searches for the automatic widget {@see RootWidget} which can handle
+ * that action.
+ */
+ _onActionDemand: function (ev) {
+ var def = this._handleAction(ev.data.actionName, ev.data.params);
+ if (ev.data.onSuccess) {
+ def.then(ev.data.onSuccess);
+ }
+ if (ev.data.onFailure) {
+ def.guardedCatch(ev.data.onFailure);
+ }
+ },
+ /**
+ * Called in response to edit mode activation -> hides the navbar.
+ *
+ * @private
+ */
+ _onEditMode: function () {
+ this.$el.addClass('editing_mode');
+ this.do_hide();
+ },
+ /**
+ * Called when a submenu is hovered -> automatically opens it if another
+ * menu was already opened.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onMenuHovered: function (ev) {
+ var $opened = this.$('> ul > li.dropdown.show');
+ if ($opened.length) {
+ $opened.find('.dropdown-toggle').dropdown('toggle');
+ $(ev.currentTarget).find('.dropdown-toggle').dropdown('toggle');
+ }
+ },
+ /**
+ * Called when the mobile menu toggle button is click -> modifies the DOM
+ * to open the mobile menu.
+ *
+ * @private
+ */
+ _onMobileMenuToggleClick: function () {
+ this.$el.parent().toggleClass('o_mobile_menu_opened');
+ },
+ /**
+ * Called in response to edit mode activation -> hides the navbar.
+ *
+ * @private
+ */
+ _onReadonlyMode: function () {
+ this.$el.removeClass('editing_mode');
+ this.do_show();
+ },
+ /**
+ * Called in response to edit mode saving -> checks if action-capable
+ * children have something to save.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSave: function (ev) {
+ ev.data.defs.push(this._handleAction('on_save'));
+ },
+});
+
+var WebsiteNavbarActionWidget = Widget.extend({
+ /**
+ * 'Action name' -> 'Handler name' object
+ *
+ * Any [data-action="x"] element inside the website navbar will
+ * automatically trigger an action "x". This action can then be handled by
+ * any `WebsiteNavbarActionWidget` instance if the action name "x" is
+ * registered in this `actions` object.
+ */
+ actions: {},
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks if the widget can execute an action whose name is given, with the
+ * given parameters. If it is the case, execute that action.
+ *
+ * @param {string} actionName
+ * @param {Array} params
+ * @returns {Promise|null} action's promise or null if no action was found
+ */
+ handleAction: function (actionName, params) {
+ var action = this[this.actions[actionName]];
+ if (action) {
+ return Promise.resolve(action.apply(this, params || []));
+ }
+ return null;
+ },
+});
+
+websiteRootData.websiteRootRegistry.add(WebsiteNavbar, '#oe_main_menu_navbar');
+
+return {
+ WebsiteNavbar: WebsiteNavbar,
+ websiteNavbarRegistry: websiteNavbarRegistry,
+ WebsiteNavbarActionWidget: WebsiteNavbarActionWidget,
+};
+});