[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) <qsm@odoo.com>
This commit is contained in:
Soukéina Bojabza
2024-02-26 21:22:46 +00:00
committed by qsm-odoo
parent c32dfae175
commit 389856bcd9
2 changed files with 144 additions and 8 deletions
+131 -7
View File
@@ -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,
+13 -1
View File
@@ -17,7 +17,19 @@
data-bs-toggle="dropdown" href="#" t-att-data-bs-display="'static' if submenu.is_mega_menu else None">
<span t-field="submenu.name"/>
</a>
<div t-if="submenu.is_mega_menu"
<div t-if="submenu.is_mega_menu and not is_mobile"
t-attf-class="dropdown-menu o_mega_menu #{submenu.mega_menu_classes}"
data-name="Mega Menu"
t-field="submenu.mega_menu_content"/>
<!--
TODO remove the next two if elements in master, this was made to not
have to change the "else" element in stable and prevent xpath fails
on the existing elements.
-> The first one render nothing (case submenu.is_mega_menu and is_mobile)
-> The second one is never considered (t-elif evaluated first)
-->
<t t-elif="submenu.is_mega_menu"/>
<div t-if="submenu.is_mega_menu" t-elif="False"
t-attf-class="dropdown-menu o_mega_menu #{submenu.mega_menu_classes}"
data-name="Mega Menu"
t-field="submenu.mega_menu_content"/>