[IMP] web: avoid useless navbar adaptations

After this commit, adaptations for the more menu behaviour are not
computed when there is no sections menu at all.
Also, before this commit, the more menu dropdown was always attached
in the dom, even if not visible. Now it is only attached when needed.

closes odoo-dev/odoo#814

Related: odoo-dev/enterprise#102
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Bruno Boi
2021-06-18 21:31:29 +02:00
committed by Aaron Bohy
parent ce275878bd
commit 788f1a8b8d
3 changed files with 135 additions and 22 deletions
@@ -8,7 +8,7 @@ import { registry } from "../../core/registry";
import { debounce } from "../../core/utils/timing";
const { Component, hooks } = owl;
const { useExternalListener } = hooks;
const { useExternalListener, useRef } = hooks;
const systrayRegistry = registry.category("systray");
export class MenuDropdown extends Dropdown {
@@ -43,6 +43,7 @@ export class NavBar extends Component {
this.currentAppSectionsExtra = [];
this.actionService = useService("action");
this.menuService = useService("menu");
this.appSubMenus = useRef("appSubMenus");
const debouncedAdapt = debounce(this.adapt.bind(this), 250);
useExternalListener(window, "resize", debouncedAdapt);
}
@@ -97,28 +98,34 @@ export class NavBar extends Component {
}
// ------- Initialize -------
// Check actual "more" dropdown state
const moreDropdown = this.el.querySelector(".o_menu_sections_more");
// Get the sectionsMenu
const sectionsMenu = this.appSubMenus.el;
if (!sectionsMenu) {
// No need to continue adaptations if there is no sections menu.
return;
}
// Save initial state to further check if new render has to be done.
const initialAppSectionsExtra = this.currentAppSectionsExtra;
const firstInitialAppSectionExtra = [...initialAppSectionsExtra].shift();
const initialAppId = firstInitialAppSectionExtra && firstInitialAppSectionExtra.appID;
// Restore (needed to get offset widths)
const sections = [
...this.el.querySelectorAll(".o_menu_sections > *:not(.o_menu_sections_more)"),
...sectionsMenu.querySelectorAll(":scope > *:not(.o_menu_sections_more)"),
];
sections.forEach((s) => s.classList.remove("d-none"));
for (const section of sections) {
section.classList.remove("d-none");
}
this.currentAppSectionsExtra = [];
moreDropdown.classList.add("d-none");
// ------- Check overflowing sections -------
const sectionsMenu = this.el.querySelector(".o_menu_sections");
const sectionsAvailableWidth = sectionsMenu.offsetWidth;
const sectionsTotalWidth = sections.reduce((sum, s) => sum + s.offsetWidth, 0);
if (sectionsAvailableWidth < sectionsTotalWidth) {
// Sections are overflowing, show "more" menu
moreDropdown.classList.remove("d-none");
let width = moreDropdown.offsetWidth;
// Sections are overflowing
// Initial width is harcoded to the width the more menu dropdown will take
let width = 46;
for (const section of sections) {
if (sectionsAvailableWidth < width + section.offsetWidth) {
// Last sections are overflowing
@@ -15,10 +15,10 @@
class="o_menu_brand"
payload="currentApp"
t-esc="currentApp.name"
t-ref="menuBrand"/>
t-ref="menuBrand" />
<!-- Current App Sections -->
<t t-call="web.NavBar.SectionsMenu">
<t t-if="currentAppSections.length" t-call="web.NavBar.SectionsMenu">
<t t-set="sections" t-value="currentAppSections" />
</t>
@@ -76,7 +76,7 @@
</t>
</t>
<t t-call="web.NavBar.SectionsMenu.MoreDropdown">
<t t-if="currentAppSectionsExtra.length" t-call="web.NavBar.SectionsMenu.MoreDropdown">
<t t-set="sections" t-value="currentAppSectionsExtra" />
<t t-if="sectionsVisibleCount lt 10">
<t t-set="hotkey" t-value="(sectionsVisibleCount + 1 % 10).toString()"/>
@@ -104,7 +104,7 @@
</t>
<t t-name="web.NavBar.SectionsMenu.MoreDropdown" owl="1">
<Dropdown class="o_menu_sections_more d-none" title="'More Menu'" hotkey="hotkey">
<Dropdown class="o_menu_sections_more" title="'More Menu'" hotkey="hotkey">
<i class="fa fa-plus"/>
<t t-set-slot="menu">
<t t-foreach="sections" t-as="section" t-key="section.id">
@@ -188,9 +188,13 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
};
baseConfig.serverData.menus = newMenus;
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
const target = getFixture();
target.style.width = "1080px";
// Set menu and mount
env.services.menu.setCurrentMenu(1);
const target = getFixture();
const navbar = await mount(MyNavbar, { env, target });
assert.containsN(
navbar.el,
@@ -198,10 +202,10 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
3,
"should have 3 menu sections displayed (that are not the 'more' menu)"
);
assert.containsOnce(
assert.containsNone(
navbar.el,
".o_menu_sections_more.d-none",
"the 'more' menu should be hidden"
".o_menu_sections_more",
"the 'more' menu should not exist"
);
// Force minimal width and dispatch window resize event
navbar.el.style.width = "0%";
@@ -234,10 +238,10 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
3,
"should have 3 menu sections displayed (that are not the 'more' menu)"
);
assert.containsOnce(
assert.containsNone(
navbar.el,
".o_menu_sections_more.d-none",
"the 'more' menu should be hidden"
".o_menu_sections_more",
"the 'more' menu should not exist"
);
// Check the navbar adaptation calls
assert.verifySteps([
@@ -248,6 +252,105 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
navbar.destroy();
});
QUnit.test("'more' menu sections adaptations do not trigger render in some cases", async (assert) => {
let adaptRunning = false;
let adaptCount = 0;
let adaptRenderCount = 0;
class MyNavbar extends NavBar {
async adapt() {
adaptRunning = true;
adaptCount++;
await super.adapt();
adaptRunning = false;
}
async render() {
if (adaptRunning) {
adaptRenderCount++;
}
await super.render(...arguments);
}
}
const newMenus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [11, 12, 13], name: "App1", appID: 1 },
11: { id: 11, children: [], name: "Section 1", appID: 1 },
12: { id: 12, children: [], name: "Section 2", appID: 1 },
13: { id: 13, children: [], name: "Section 3", appID: 1 },
};
baseConfig.serverData.menus = newMenus;
// Force the parent width, to make this test independent of screen size
const target = getFixture();
target.style.width = "600px";
const env = await makeTestEnv(baseConfig);
const navbar = await mount(MyNavbar, { env, target });
assert.strictEqual(navbar.currentAppSections.length, 0, "0 app sub menus");
assert.strictEqual(navbar.el.offsetWidth, 600);
assert.strictEqual(adaptCount, 1);
assert.strictEqual(
adaptRenderCount, 0,
"during adapt, render not triggered as the navbar has no app sub menus"
);
// Force minimal width and dispatch window resize event
navbar.el.style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(navbar.el.offsetWidth, 0);
assert.strictEqual(adaptCount, 2);
assert.strictEqual(
adaptRenderCount, 0,
"during adapt, render not triggered as the navbar has no app sub menus"
);
// Set menu
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.strictEqual(navbar.currentAppSections.length, 3, "3 app sub menus");
assert.strictEqual(
navbar.currentAppSectionsExtra.length, 3,
"all app sub menus are inside the more menu"
);
assert.strictEqual(adaptCount, 3);
assert.strictEqual(
adaptRenderCount, 1,
"during adapt, render triggered as the navbar does not have enough space for app sub menus"
);
// Force 40% width and dispatch window resize event
navbar.el.style.width = "40%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(
navbar.currentAppSectionsExtra.length, 3,
"all app sub menus are STILL inside the more menu"
);
assert.strictEqual(adaptCount, 4);
assert.strictEqual(
adaptRenderCount, 1,
"during adapt, render not triggered as the more menu dropdown is STILL the same"
);
// Reset to full width and dispatch window resize event
navbar.el.style.width = "100%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(navbar.currentAppSections.length, 3, "still 3 app sub menus");
assert.strictEqual(
navbar.currentAppSectionsExtra.length, 0,
"all app sub menus are NO MORE inside the more menu"
);
assert.strictEqual(adaptCount, 5);
assert.strictEqual(
adaptRenderCount, 2,
"during adapt, render triggered as the more menu dropdown is NO MORE the same"
);
navbar.destroy();
});
QUnit.test("'more' menu sections properly updated on app change", async (assert) => {
const newMenus = {
root: { id: "root", children: [1, 2], name: "root", appID: "root" },
@@ -271,9 +374,12 @@ QUnit.test("'more' menu sections properly updated on app change", async (assert)
baseConfig.serverData.menus = newMenus;
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
const target = getFixture();
target.style.width = "1080px";
// Set App1 menu and mount
env.services.menu.setCurrentMenu(1);
const target = getFixture();
const navbar = await mount(NavBar, { env, target });
// Force minimal width and dispatch window resize event