From c0f68e903708360b1f091041d85a21f8b2bebd3a Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Mon, 22 Nov 2021 16:25:11 +0000 Subject: [PATCH] [FIX] web: webclient: navbar: consistently adapt menu items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use case happens in studio. have some overflowing app submenus in an app go into studio refresh with a simple F5. Before this commit, the navbar's overflowing buttons wouldn't put themselves inside the "more" dropdown This was because the studio client action also reacts to change in the menu service, so the technical flow was: - laodState (webClient) - setMenu (MENU-CHANGED on the bus), then because of it: - studio client action > render (1) - studio navbar > render (with adapt right after that render!) (2) - studio navbar > render (because of 1) which cancels the render in 2 without cancelling the promise, that is adapt is still called. To remedy this, the standard use of useEffect and useBus are is put into place. After this commit, the studio navbar has its overflowing menu items in the dropdown. The adapt algorithm now uses getBoundingClientRect to get the not rounded width of elements that should be adapted, otherwise, slight differences in browser's computation and rounding of offsetWidth may influence test results. closes odoo/odoo#80353 X-original-commit: 231902738287a897f50a1711f991bc55928b77d9 Related: odoo/enterprise#22498 Signed-off-by: Géry Debongnie (ged) Signed-off-by: Lucas Perais (lpe) --- .../web/static/src/webclient/navbar/navbar.js | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/addons/web/static/src/webclient/navbar/navbar.js b/addons/web/static/src/webclient/navbar/navbar.js index bbc64f80cf1..ad96358f9e9 100644 --- a/addons/web/static/src/webclient/navbar/navbar.js +++ b/addons/web/static/src/webclient/navbar/navbar.js @@ -2,7 +2,7 @@ import { Dropdown } from "@web/core/dropdown/dropdown"; import { DropdownItem } from "@web/core/dropdown/dropdown_item"; -import { useEffect, useService, onDestroyed } from "@web/core/utils/hooks"; +import { useEffect, useService, onDestroyed, useBus } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; import { debounce } from "@web/core/utils/timing"; import { ErrorHandler, NotUpdatable } from "@web/core/utils/components"; @@ -11,6 +11,8 @@ const { Component, hooks } = owl; const { useExternalListener, useRef } = hooks; const systrayRegistry = registry.category("systray"); +const getBoundingClientRect = Element.prototype.getBoundingClientRect; + export class MenuDropdown extends Dropdown { setup() { super.setup(); @@ -55,21 +57,18 @@ export class NavBar extends Component { const debouncedAdapt = debounce(this.adapt.bind(this), 250); onDestroyed(() => debouncedAdapt.cancel()); useExternalListener(window, "resize", debouncedAdapt); - } - mounted() { - this.adapt(); - const renderAndAdapt = async () => { - await this.render(); - await this.adapt(); + let adaptCounter = 0; + const renderAndAdapt = () => { + adaptCounter++; + this.render(); }; - systrayRegistry.on("UPDATE", this, renderAndAdapt); - this.env.bus.on("MENUS:APP-CHANGED", this, renderAndAdapt); - } - willUnmount() { - systrayRegistry.off("UPDATE", this); - this.env.bus.off("MENUS:APP-CHANGED", this); + useBus(systrayRegistry, "UPDATE", renderAndAdapt); + useBus(this.env.bus, "MENUS:APP-CHANGED", renderAndAdapt); + // We don't want to adapt every time we are patched + // rather, we adapt only when menus or systrays have changed. + useEffect(() => {this.adapt();}, () => [adaptCounter]); } handleItemError(error, item) { @@ -138,8 +137,10 @@ export class NavBar extends Component { this.currentAppSectionsExtra = []; // ------- Check overflowing sections ------- - const sectionsAvailableWidth = sectionsMenu.offsetWidth; - const sectionsTotalWidth = sections.reduce((sum, s) => sum + s.offsetWidth, 0); + // use getBoundingClientRect to get unrounded values for width in order to avoid rounding problem + // with offsetWidth. + const sectionsAvailableWidth = getBoundingClientRect.call(sectionsMenu).width; + const sectionsTotalWidth = sections.reduce((sum, s) => sum + getBoundingClientRect.call(s).width, 0); if (sectionsAvailableWidth < sectionsTotalWidth) { // Sections are overflowing // Initial width is harcoded to the width the more menu dropdown will take