From 389856bcd94d459d72f46c2a517afb3f6d976e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Souk=C3=A9ina=20Bojabza?= Date: Thu, 11 Jan 2024 10:15:50 +0100 Subject: [PATCH] [FIX] website: fix duplicated mega menu synchronization issues Since PR [1], which redesigned the headers, there are now two navbars: one for the desktop view and one for the mobile view. This also means that the different menus are in both navbars at the same time. However, this causes some issues with the mega menus. Indeed, since each mega menu is in both navbars, the changes made in one are also made in the other (since it is a field in the database). This causes issues when drag and dropping: - Drag and drop a mega menu element. - Re-drag it and drop it somewhere else. => We notice that the element was cloned and each drag and drop adds a new clone. Moreover, when clicking on a link inside these elements, a traceback appears. This happens because since both mega menus are synced, the drag and drop clones are also synced and so the dragged element is dropped multiple times (after each clone). This commit fixes these synchronization issues by adding the mega menus only once in the DOM, in the desktop navbar. They are then moved from one navbar to the other when we open them. [1]: https://github.com/odoo/odoo/pull/119650 task-3609531 opw-3730165 closes odoo/odoo#146492 Related: odoo/design-themes#756 Signed-off-by: Quentin Smetz (qsm) --- addons/website/static/src/js/content/menu.js | 138 ++++++++++++++++++- addons/website/views/website_templates.xml | 14 +- 2 files changed, 144 insertions(+), 8 deletions(-) diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js index d6959ca9766..e61a0df9022 100644 --- a/addons/website/static/src/js/content/menu.js +++ b/addons/website/static/src/js/content/menu.js @@ -37,7 +37,7 @@ const BaseAnimatedHeader = animations.Animation.extend({ start: function () { this.$main = this.$el.next('main'); this.isOverlayHeader = !!this.$el.closest('.o_header_overlay, .o_header_overlay_theme').length; - this.$dropdowns = this.$el.find('.dropdown, .dropdown-menu'); + this.$dropdowns = this.$el.find('.dropdown, .dropdown-menu'); // TODO remove in master this.hiddenOnScrollEl = this.el.querySelector(".o_header_hide_on_scroll"); // While scrolling through navbar menus on medium devices, body should @@ -252,12 +252,10 @@ const BaseAnimatedHeader = animations.Animation.extend({ } if (this.closeOpenedMenus) { - // TODO master: make this.$dropdowns the .dropdown-toggle directly. - for (const dropdownMenuEl of this.$dropdowns) { - Dropdown.getOrCreateInstance( - dropdownMenuEl.closest('.dropdown').querySelector('.dropdown-toggle') - ).hide(); - } + // Hide only the open dropdowns. + this.el.querySelectorAll(".dropdown-toggle.show").forEach(dropdownToggleEl => { + Dropdown.getOrCreateInstance(dropdownToggleEl).hide(); + }); } }, /** @@ -684,6 +682,132 @@ publicWidget.registry.hoverableDropdown = animations.Animation.extend({ }, }); +publicWidget.registry.MegaMenuDropdown = publicWidget.Widget.extend({ + selector: "header#top", + disabledInEditableMode: false, + events: { + "mousedown .o_mega_menu_toggle": "_onMegaMenuClick", + "mouseenter .o_mega_menu_toggle": "_onMegaMenuHover", + "mousedown .o_extra_menu_items": "_onExtraMenuClick", + "keyup .o_mega_menu_toggle": "_onMegaMenuClick", + "keyup .o_extra_menu_items": "_onExtraMenuClick", + }, + + /** + * @override + */ + start() { + const toggleEls = this.el.querySelectorAll(".o_mega_menu_toggle"); + this.desktopMegaMenuToggleEls = []; + this.mobileMegaMenuToggleEls = []; + for (const el of toggleEls) { + if (el.closest(".o_header_mobile")) { + this.mobileMegaMenuToggleEls.push(el); + } else { + this.desktopMegaMenuToggleEls.push(el); + } + } + + // TODO: remove in master. + // If the mega menus are duplicated (i.e. they are in both desktop and + // mobile view navbars), only keep one of them. Indeed, having the same + // mega menu multiple times can cause some issues when editing. + // Note: The XML templates have been modified to add them only once in + // the DOM. This code is needed to fix the issues for databases created + // before the templates modifications. + this.mobileMegaMenuToggleEls.forEach((megaMenuToggleEl, i) => { + const desktopMenuEl = this.desktopMegaMenuToggleEls[i].parentElement.querySelector(".o_mega_menu"); + if (!desktopMenuEl) { + return; + } + megaMenuToggleEl.parentElement.querySelector(".o_mega_menu")?.remove(); + }); + + return this._super(...arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * If the mega menu dropdown on which we are clicking/hovering does not have + * a mega menu (i.e. it is in the other navbar), brings the corresponding + * mega menu into it. + * + * @private + * @param {Element} megaMenuToggleEl the clicked/hovered mega menu dropdown + */ + _moveMegaMenu(megaMenuToggleEl) { + const hasMegaMenu = !!megaMenuToggleEl.parentElement.querySelector(".o_mega_menu"); + if (hasMegaMenu) { + return; + } + this.options.wysiwyg?.odooEditor.observerUnactive("moveMegaMenu"); + const isMobileNavbar = !!megaMenuToggleEl.closest(".o_header_mobile"); + const currentNavbarToggleEls = isMobileNavbar ? + this.mobileMegaMenuToggleEls : this.desktopMegaMenuToggleEls; + const otherNavbarToggleEls = isMobileNavbar ? + this.desktopMegaMenuToggleEls : this.mobileMegaMenuToggleEls; + const megaMenuToggleIndex = currentNavbarToggleEls.indexOf(megaMenuToggleEl); + const previousMegaMenuToggleEl = otherNavbarToggleEls[megaMenuToggleIndex]; + const megaMenuEl = previousMegaMenuToggleEl.parentElement.querySelector(".o_mega_menu"); + // Hiding the dropdown where the mega menu comes from before moving it, + // so everything is in a consistent state. + Dropdown.getOrCreateInstance(previousMegaMenuToggleEl).hide(); + megaMenuToggleEl.insertAdjacentElement("afterend", megaMenuEl); + this.options.wysiwyg?.odooEditor.observerActive("moveMegaMenu"); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Called when a mega menu dropdown is clicked/key pressed. + * + * @private + * @param {Event} ev + */ + _onMegaMenuClick(ev) { + const megaMenuToggleEl = ev.currentTarget; + // Ignore the event if the menus are hoverable and in desktop view (the + // hoverable menus are clicked on mobile view), but not if we used the + // keyboard. + if (this.el.classList.contains("o_hoverable_dropdown") + && !megaMenuToggleEl.closest(".o_header_mobile") && ev.type !== "keyup") { + return; + } + this._moveMegaMenu(megaMenuToggleEl); + }, + /** + * Called when a mega menu dropdown is hovered. + * + * @private + * @param {Event} ev + */ + _onMegaMenuHover(ev) { + const megaMenuToggleEl = ev.currentTarget; + // Ignore the event if the menus are not hoverable or if we are in + // mobile view (again, the hoverable menus are clicked on mobile view). + if (!this.el.classList.contains("o_hoverable_dropdown") + || megaMenuToggleEl.closest(".o_header_mobile") && uiUtils.getSize() <= SIZES.SM) { + return; + } + this._moveMegaMenu(megaMenuToggleEl); + }, + /** + * Called when the extra menu (+) dropdown is clicked/key pressed. + * + * @private + * @param {Event} ev + */ + _onExtraMenuClick(ev) { + const megaMenuToggleEls = ev.currentTarget.querySelectorAll(".o_mega_menu_toggle"); + megaMenuToggleEls.forEach(megaMenuToggleEl => this._moveMegaMenu(megaMenuToggleEl)); + }, +}); + publicWidget.registry.HeaderGeneral = publicWidget.Widget.extend({ selector: 'header#top', disabledInEditableMode: false, diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index f4e580f48d2..688725465f5 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -17,7 +17,19 @@ data-bs-toggle="dropdown" href="#" t-att-data-bs-display="'static' if submenu.is_mega_menu else None"> -
+ + +