[REF] web: remove dropdown-item-selected event

In owl 2, the t-on directive is no longer supported on components. The
way to handle events on components is to give them a callback as a prop.
This already works in owl 1, so this commit prepares the code for an
easier migration in the future by removing the "dropdown-item-selected"
event and converting the code that was using it to use the new paradigm.

closes odoo/odoo#80684

Related: odoo/enterprise#22722
Signed-off-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
Samuel Degueldre
2021-12-14 12:40:19 +00:00
parent 671ed63b2a
commit 57a02f0a14
33 changed files with 180 additions and 217 deletions
@@ -12,7 +12,7 @@
<t t-foreach="elements" t-as="element" t-key="element_index">
<DropdownItem
t-if="element.type == 'item'"
t-on-dropdown-item-selected.stop="element.callback"
onSelected="element.callback"
href="element.href"
t-esc="element.description"
/>
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.DebugMenu.ProfilingItem" owl="1">
<DropdownItem payload="{}">
<DropdownItem>
<div class="o_debug_profiling_item">
<span class="o_profiling_switch">
<span class="custom-control custom-switch" t-on-click.stop.prevent="profiling.toggleProfiling()">
+15 -23
View File
@@ -3,7 +3,6 @@
import { useBus, useEffect, useService } from "@web/core/utils/hooks";
import { usePosition } from "../position/position_hook";
import { useDropdownNavigation } from "./dropdown_navigation_hook";
import { ParentClosingMode } from "./dropdown_item";
import { localization } from "../l10n/localization";
const { Component, core, hooks, useState, QWeb } = owl;
@@ -17,6 +16,8 @@ const DIRECTION_CARET_CLASS = {
right: "dropright",
};
export const DROPDOWN = Symbol("Dropdown");
/**
* @typedef DropdownState
* @property {boolean} open
@@ -66,8 +67,18 @@ export class Dropdown extends Component {
);
// Set up nested dropdowns ---------------------------------------------
this.hasParentDropdown = this.env.inDropdown;
useSubEnv({ inDropdown: true });
this.parentDropdown = this.env[DROPDOWN];
useSubEnv({
[DROPDOWN]: {
close: this.close.bind(this),
closeAllParents: () => {
this.close();
if (this.parentDropdown) {
this.parentDropdown.closeAllParents();
}
},
},
});
// Set up key navigation -----------------------------------------------
useDropdownNavigation();
@@ -75,7 +86,7 @@ export class Dropdown extends Component {
// Set up toggler and positioning --------------------------------------
/** @type {string} **/
let position =
this.props.position || (this.hasParentDropdown ? "right-start" : "bottom-start");
this.props.position || (this.parentDropdown ? "right-start" : "bottom-start");
let [direction, variant = "middle"] = position.split("-");
if (localization.direction === "rtl") {
if (["bottom", "top"].includes(direction)) {
@@ -180,25 +191,6 @@ export class Dropdown extends Component {
// Handlers
// -------------------------------------------------------------------------
/**
* Checks if should close on dropdown item selection.
*
* @param {CustomEvent<import("./dropdown_item").DropdownItemSelectedEventDetail>} ev
*/
onItemSelected(ev) {
// Handle parent closing request
const { dropdownClosingRequest } = ev.detail;
const closeAll = dropdownClosingRequest.mode === ParentClosingMode.AllParents;
const closeSelf =
dropdownClosingRequest.isFresh &&
dropdownClosingRequest.mode === ParentClosingMode.ClosestParent;
if (!this.props.manualOnly && (closeAll || closeSelf)) {
this.close();
}
// Mark closing request as started
ev.detail.dropdownClosingRequest.isFresh = false;
}
/**
* Dropdowns react to each other state changes through this method.
*
@@ -4,14 +4,13 @@
<t t-name="web.Dropdown" owl="1">
<div
class="o-dropdown dropdown"
t-att-class="{ show: state.open, 'o-dropdown--no-caret': !hasParentDropdown }"
t-att-class="{ show: state.open, 'o-dropdown--no-caret': !parentDropdown }"
t-attf-class="{{ directionCaretClass || '' }}"
t-on-dropdown-item-selected="onItemSelected"
>
<button
t-if="props.toggler !== 'parent'"
class="dropdown-toggle"
t-attf-class="{{props.togglerClass || ''}} {{hasParentDropdown ? 'dropdown-item' : ''}}"
t-attf-class="{{props.togglerClass || ''}} {{parentDropdown ? 'dropdown-item' : ''}}"
t-on-click="onTogglerClick"
t-on-mouseenter="onTogglerMouseEnter"
t-att-title="props.title"
@@ -1,11 +1,11 @@
/** @odoo-module **/
import { DROPDOWN } from "./dropdown";
const { Component, QWeb } = owl;
/**
* @enum {string}
*/
export const ParentClosingMode = {
const ParentClosingMode = {
None: "none",
ClosestParent: "closest",
AllParents: "all",
@@ -26,29 +26,48 @@ export const ParentClosingMode = {
*/
export class DropdownItem extends Component {
/**
* Triggers a custom DropdownItemSelectedEvent
* Tells the parent dropdown that an item was selected and closes the
* parent(s) dropdown according the the parentClosingMode prop.
*
* @param {MouseEvent} ev
*/
onClick(ev) {
if (this.props.href){
const { href, onSelected, parentClosingMode } = this.props;
if (href) {
ev.preventDefault();
}
/** @type DropdownItemSelectedEventDetail */
const detail = {
payload: this.props.payload,
dropdownClosingRequest: {
isFresh: true,
mode: this.props.parentClosingMode,
},
};
this.trigger("dropdown-item-selected", detail);
if (onSelected) {
onSelected();
}
const dropdown = this.env[DROPDOWN];
if (!dropdown) {
return;
}
const { ClosestParent, AllParents } = ParentClosingMode;
switch (parentClosingMode) {
case ClosestParent:
dropdown.close();
break;
case AllParents:
dropdown.closeAllParents();
break;
}
}
get dataAttributes() {
const { dataset } = this.props;
if (this.props.dataset) {
const attributes = Object.entries(dataset).map(([key, value]) => {
return [`data-${key.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`, value];
});
return Object.fromEntries(attributes);
}
return {};
}
}
DropdownItem.template = "web.DropdownItem";
DropdownItem.props = {
payload: {
type: Object,
onSelected: {
type: Function,
optional: true,
},
parentClosingMode: {
@@ -67,6 +86,10 @@ DropdownItem.props = {
type: String,
optional: true,
},
dataset: {
type: Object,
optional: true,
},
};
DropdownItem.defaultProps = {
parentClosingMode: ParentClosingMode.AllParents,
@@ -9,6 +9,7 @@
t-on-click="onClick"
t-att-title="props.title"
t-att-data-hotkey="props.hotkey"
t-att="dataAttributes"
>
<t t-slot="default" />
</t>
@@ -36,14 +36,14 @@ export function useDropdownNavigation() {
// As this navigation hook relies on clicking ".dropdown-toggle" elements,
// it is incompatible with a toggler="parent" strategy for subdropdowns.
if (comp.hasParentDropdown && comp.props.toggler === "parent") {
if (comp.parentDropdown && comp.props.toggler === "parent") {
throw new Error("A nested Dropdown must use its standard toggler");
}
// Needed to avoid unwanted mouseclick behavior on a subdropdown toggler.
const originalOnTogglerClick = comp.onTogglerClick.bind(comp);
comp.onTogglerClick = (ev) => {
if (comp.hasParentDropdown && !ev.__fromDropdownNavigation) {
if (comp.parentDropdown && !ev.__fromDropdownNavigation) {
return;
}
originalOnTogglerClick();
@@ -52,7 +52,7 @@ export function useDropdownNavigation() {
// Needed to avoid unwanted mouseenter behavior on a subdropdown toggler.
const originalOnTogglerMouseEnter = comp.onTogglerMouseEnter.bind(comp);
comp.onTogglerMouseEnter = () => {
if (comp.hasParentDropdown) {
if (comp.parentDropdown) {
return;
}
originalOnTogglerMouseEnter();
@@ -192,7 +192,7 @@ export function useDropdownNavigation() {
useEffect(
(open) => {
// If we just opened and we are a subdropdown, make active our first menu element.
if (open && comp.hasParentDropdown) {
if (open && comp.parentDropdown) {
setActiveMenuElement("FIRST");
}
},
@@ -201,7 +201,7 @@ export function useDropdownNavigation() {
// Set up keyboard navigation ----------------------------------------------
const hotkeyService = useService("hotkey");
const closeSubDropdown = comp.hasParentDropdown ? comp.close : () => {};
const closeSubDropdown = comp.parentDropdown ? comp.close : () => {};
const openSubDropdown = () => {
const menuElement = getActiveMenuElement();
// Active menu element is a sub dropdown
@@ -22,10 +22,9 @@ odoo.define("web.ComparisonMenu", function (require) {
/**
* @private
* @param {OwlEvent} ev
* @param {number} itemId
*/
onComparisonSelected(ev) {
const { itemId } = ev.detail.payload;
onComparisonSelected(itemId) {
this.model.dispatch("toggleComparison", itemId);
}
}
@@ -64,11 +64,10 @@ odoo.define('web.FavoriteMenu', function (require) {
/**
* @private
* @param {OwlEvent} ev
* @param {number} itemId
*/
onFavoriteSelected(ev) {
ev.stopPropagation();
this.model.dispatch('toggleFilter', ev.detail.payload.itemId);
onFavoriteSelected(itemId) {
this.model.dispatch('toggleFilter', itemId);
}
/**
@@ -39,11 +39,11 @@ odoo.define('web.FilterMenu', function (require) {
/**
* @private
* @param {OwlEvent} ev
* @param {Object} param0
* @param {number} param0.itemId
* @param {number} [param0.optionId]
*/
onFilterSelected(ev) {
ev.stopPropagation();
const { itemId, optionId } = ev.detail.payload;
onFilterSelected({ itemId, optionId }) {
if (optionId) {
this.model.dispatch('toggleFilterWithOptions', itemId, optionId);
} else {
@@ -53,12 +53,11 @@ odoo.define('web.GroupByMenu', function (require) {
//---------------------------------------------------------------------
/**
* @private
* @param {OwlEvent} ev
* @param {Object} param0
* @param {number} param0.itemId
* @param {number} [param0.optionId]
*/
onGroupBySelected(ev) {
ev.stopPropagation();
const { itemId, optionId } = ev.detail.payload;
onGroupBySelected({ itemId, optionId }) {
if (optionId) {
this.model.dispatch('toggleFilterWithOptions', itemId, optionId);
} else {
+2 -8
View File
@@ -98,20 +98,14 @@
<t t-esc="item.description"/>
</t>
<t t-foreach="Object.values(intervalOptions)" t-as="option" t-key="option.id">
<DropdownItem
class="o_item_option dropdown-item"
t-on-dropdown-item-selected.stop="_onClickMenuGroupBy(item.fieldName, option.id)"
>
<DropdownItem class="o_item_option dropdown-item" onSelected="() => this._onClickMenuGroupBy(item.fieldName, option.id)">
<t t-esc="option.description"/>
</DropdownItem>
</t>
</Dropdown>
</t>
<t t-else="">
<DropdownItem
class="o_menu_item dropdown-item"
t-on-dropdown-item-selected.stop="_onClickMenuGroupBy(item.fieldName, null)"
>
<DropdownItem class="o_menu_item dropdown-item" onSelected="() => this._onClickMenuGroupBy(item.fieldName, null)">
<t t-esc="item.description"/>
</DropdownItem>
</t>
@@ -22,10 +22,9 @@ export class ComparisonMenu extends Component {
}
/**
* @param {CustomEvent}
* @param {number} itemId
*/
onComparisonSelected(ev) {
const { itemId } = ev.detail.payload;
onComparisonSelected(itemId) {
this.env.searchModel.toggleSearchItem(itemId);
}
}
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.ComparisonMenu" owl="1">
<Dropdown class="o_comparison_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onComparisonSelected">
<Dropdown class="o_comparison_menu btn-group" togglerClass="'btn btn-light'">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Comparison</span>
@@ -11,7 +11,7 @@
<DropdownItem
class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
onSelected="() => this.onComparisonSelected(item.id)"
parentClosingMode="'none'"
t-esc="item.description"
/>
@@ -39,10 +39,9 @@ export class FavoriteMenu extends Component {
}
/**
* @param {CustomEvent} ev
* @param {number} itemId
*/
onFavoriteSelected(ev) {
const { itemId } = ev.detail.payload;
onFavoriteSelected(itemId) {
this.env.searchModel.toggleSearchItem(itemId);
}
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.FavoriteMenu" owl="1">
<Dropdown class="o_favorite_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onFavoriteSelected">
<Dropdown class="o_favorite_menu btn-group" togglerClass="'btn btn-light'">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Favorites</span>
@@ -15,8 +15,8 @@
<t t-if="item.type ==='favorite'">
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
onSelected="() => this.onFavoriteSelected(item.id)"
>
<span class="d-flex p-0 align-items-center justify-content-between">
<t t-esc="item.description"/>
@@ -28,7 +28,7 @@
</DropdownItem>
</t>
<t t-else="">
<t t-component="item.Component" t-on-dropdown-item-selected.stop=""/>
<t t-component="item.Component"/>
</t>
<t t-set="currentGroup" t-value="item.groupNumber"/>
</t>
@@ -23,10 +23,11 @@ export class FilterMenu extends Component {
}
/**
* @param {CustomEvent} ev
* @param {Object} param0
* @param {number} param0.itemId
* @param {number} [param0.optionId]
*/
onFilterSelected(ev) {
const { itemId, optionId } = ev.detail.payload;
onFilterSelected({ itemId, optionId }) {
if (optionId) {
this.env.searchModel.toggleDateFilter(itemId, optionId);
} else {
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.FilterMenu" owl="1">
<Dropdown class="o_filter_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onFilterSelected">
<Dropdown class="o_filter_menu btn-group" togglerClass="'btn btn-light'">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Filters</span>
@@ -24,9 +24,9 @@
</t>
<DropdownItem class="o_item_option"
t-att-class="{ selected: option.isActive }"
payload="{ itemId: item.id, optionId: option.id }"
parentClosingMode="'none'"
t-esc="option.description"
onSelected="() => this.onFilterSelected({ itemId: item.id, optionId: option.id })"
/>
<t t-set="subGroup" t-value="option.groupNumber"/>
</t>
@@ -35,9 +35,9 @@
<t t-else="">
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
t-esc="item.description"
onSelected="() => this.onFilterSelected({ itemId: item.id })"
/>
</t>
<t t-set="currentGroup" t-value="item.groupNumber"/>
@@ -45,10 +45,11 @@ export class GroupByMenu extends Component {
}
/**
* @param {CustomEvent} ev
* @param {Object} param0
* @param {number} param0.itemId
* @param {number} [param0.optionId]
*/
onGroupBySelected(ev) {
const { itemId, optionId } = ev.detail.payload;
onGroupBySelected({ itemId, optionId }) {
if (optionId) {
this.env.searchModel.toggleDateGroupBy(itemId, optionId);
} else {
@@ -5,7 +5,6 @@
<Dropdown class="o_group_by_menu btn-group"
togglerClass="'btn btn-light'"
t-props="dropdownProps"
t-on-dropdown-item-selected="onGroupBySelected"
>
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
@@ -28,9 +27,9 @@
</t>
<DropdownItem class="o_item_option"
t-att-class="{ selected: option.isActive }"
payload="{ itemId: item.id, optionId: option.id }"
parentClosingMode="'none'"
t-esc="option.description"
onSelected="() => this.onGroupBySelected({ itemId: item.id, optionId: option.id})"
/>
<t t-set="subGroup" t-value="option.groupNumber"/>
</t>
@@ -39,9 +38,9 @@
<t t-else="">
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
t-esc="item.description"
onSelected="() => this.onGroupBySelected({ itemId: item.id })"
/>
</t>
<t t-set="currentGroup" t-value="item.groupNumber"/>
@@ -98,10 +98,10 @@ export class GraphView extends Component {
}
/**
* @param {CustomEvent} ev
* @param {Object} param0
* @param {string} param0.measure
*/
onMeasureSelected(ev) {
const { measure } = ev.detail.payload;
onMeasureSelected({ measure }) {
this.model.updateMetaData({ measure });
}
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.GraphView.Buttons" owl="1">
<div class="btn-group" role="toolbar" aria-label="Main actions" t-on-dropdown-item-selected="onMeasureSelected">
<div class="btn-group" role="toolbar" aria-label="Main actions">
<t t-call="web.ReportViewMeasures">
<t t-set="measures" t-value="model.metaData.measures"/>
<t t-set="activeMeasures" t-value="[model.metaData.measure]"/>
@@ -44,13 +44,17 @@ export class PivotGroupByMenu extends GroupByMenu {
}
/**
* @override
* @param {CustomEvent} ev
* @param {Object} param0
* @param {number} param0.itemId
* @param {number} [param0.optionId]
*/
onGroupBySelected(ev) {
const { itemId, optionId } = ev.detail.payload;
onGroupBySelected({ itemId, optionId }) {
// Here, we purposely do not call super.onGroupBySelected as we don't want
// to change the group-by on the model, only inside the pivot
const item = this.items.find(({ id }) => id === itemId);
// Augment payload data
Object.assign(ev.detail.payload, {
this.props.onItemSelected({
itemId,
optionId,
fieldName: item.fieldName,
interval: optionId,
groupId: this.props.cell.groupId,
@@ -84,10 +84,10 @@ export class PivotRenderer extends Component {
* Handle the selection of a groupby dropdown item.
*
* @param {"col"|"row"} type
* @param {CustomEvent} ev
* @param {Object} payload
*/
onDropdownItemSelected(type, ev) {
this.model.addGroupBy({ ...ev.detail.payload, type });
onGroupBySelected(type, payload) {
this.model.addGroupBy({ ...payload, type });
}
/**
* Handle a click on a header cell.
@@ -73,7 +73,7 @@
customGroupBys="model.metaData.customGroupBys"
showActiveItems="false"
t-on-click.stop=""
t-on-dropdown-item-selected="onDropdownItemSelected(isXAxis ? 'col' : 'row')"
onItemSelected="payload => this.onGroupBySelected(isXAxis ? 'col' : 'row', payload)"
t-on-add-custom-group-by="onAddCustomGroupBy(isXAxis ? 'col' : 'row')"
/>
</t>
@@ -108,10 +108,11 @@ export class PivotView extends Component {
/**
* Toggles the given measure
*
* @param {CustomEvent} ev
* @param {Object} param0
* @param {string} param0.measure
*/
onMeasureSelected(ev) {
this.model.toggleMeasure(ev.detail.payload.measure);
onMeasureSelected({ measure }) {
this.model.toggleMeasure(measure);
}
/**
* @param {CustomEvent} ev
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.PivotView.Buttons" owl="1">
<div class="btn-group" role="toolbar" aria-label="Main actions" t-on-dropdown-item-selected="onMeasureSelected">
<div class="btn-group" role="toolbar" aria-label="Main actions">
<t t-call="web.ReportViewMeasures">
<t t-set="measures" t-value="model.metaData.measures"/>
<t t-set="activeMeasures" t-value="model.metaData.activeMeasures"/>
+1 -1
View File
@@ -16,8 +16,8 @@
<div t-if="!measure_first and measure == '__count'" role="separator" class="dropdown-divider"/>
<DropdownItem class="o_menu_item dropdown-item"
t-att-class="{ selected: activeMeasures.includes(measure) }"
payload="{ measure: measure_value.name }"
parentClosingMode="'none'"
onSelected="() => this.onMeasureSelected({ measure: measure_value.name })"
>
<t t-esc="measures[measure].string"/>
</DropdownItem>
@@ -31,23 +31,6 @@ MenuDropdown.props.xmlid = {
optional: true,
};
export class MenuItem extends DropdownItem {
setup() {
super.setup();
useEffect(
() => {
if (this.props.payload.id) {
this.el.dataset.section = this.props.payload.id;
}
if (this.props.payload.xmlid) {
this.el.dataset.menuXmlid = this.props.payload.xmlid;
}
},
() => []
);
}
}
export class NavBar extends Component {
setup() {
this.currentAppSectionsExtra = [];
@@ -68,7 +51,12 @@ export class NavBar extends Component {
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]);
useEffect(
() => {
this.adapt();
},
() => [adaptCounter]
);
}
handleItemError(error, item) {
@@ -140,7 +128,10 @@ export class NavBar extends Component {
// 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);
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
@@ -180,8 +171,7 @@ export class NavBar extends Component {
return this.render();
}
onNavBarDropdownItemSelection(ev) {
const { payload: menu } = ev.detail;
onNavBarDropdownItemSelection(menu) {
if (menu) {
this.menuService.selectMenu(menu);
}
@@ -196,4 +186,4 @@ export class NavBar extends Component {
}
}
NavBar.template = "web.NavBar";
NavBar.components = { MenuDropdown, MenuItem, NotUpdatable, ErrorHandler };
NavBar.components = { MenuDropdown, DropdownItem, NotUpdatable, ErrorHandler };
@@ -5,7 +5,6 @@
<header>
<nav
class="o_main_navbar"
t-on-dropdown-item-selected="onNavBarDropdownItemSelection"
data-command-category="navbar"
>
<!-- Apps Menu -->
@@ -14,13 +13,14 @@
</t>
<!-- App Brand -->
<MenuItem
<DropdownItem
t-if="currentApp and !env.isSmall"
href="getMenuItemHref(currentApp)"
payload="currentApp"
t-esc="currentApp.name"
class="o_menu_brand"
t-ref="menuBrand"
dataset="{ menuXmlid: currentApp.xmlid, section: currentApp.id }"
onSelected="() => this.onNavBarDropdownItemSelection(currentApp)"
/>
<!-- Current App Sections -->
@@ -47,15 +47,16 @@
<t t-set-slot="toggler">
<i class="fa fa-th-large" />
</t>
<MenuItem
<DropdownItem
t-foreach="apps"
t-as="app"
t-key="app.id"
class="o_app"
t-att-class="{ focus: menuService.getCurrentApp() === app }"
href="getMenuItemHref(app)"
payload="app"
t-esc="app.name"
t-esc="app.name"
dataset="{ menuXmlid: app.xmlid, section: app.id }"
onSelected="() => this.onNavBarDropdownItemSelection(app)"
/>
</Dropdown>
</t>
@@ -77,13 +78,14 @@
</t>
<t t-if="!section.childrenTree.length">
<MenuItem
<DropdownItem
title="section.name"
class="o_nav_entry"
payload="section"
href="getMenuItemHref(section)"
hotkey="hotkey"
t-esc="section.name"
dataset="{ menuXmlid: section.xmlid, section: section.id }"
onSelected="() => this.onNavBarDropdownItemSelection(section)"
/>
</t>
<t t-else="">
@@ -114,24 +116,26 @@
<t t-name="web.NavBar.SectionsMenu.Dropdown.MenuSlot" owl="1">
<t t-foreach="items" t-as="item" t-key="item.id">
<MenuItem
<DropdownItem
t-if="!item.childrenTree.length"
payload="item"
href="getMenuItemHref(item)"
class="dropdown-item"
t-esc="item.name"
dataset="{ menuXmlid: item.xmlid, section: item.id }"
onSelected="() => this.onNavBarDropdownItemSelection(item)"
/>
<t t-else="">
<div class="dropdown-menu_group dropdown-header" t-esc="item.name" />
<MenuItem
<DropdownItem
t-foreach="item.childrenTree"
t-as="subItem"
t-key="subItem.id"
class="o_dropdown_menu_group_entry dropdown-item"
payload="subItem"
href="getMenuItemHref(subItem)"
t-esc="subItem.name"
dataset="{ menuXmlid: subItem.xmlid, section: subItem.id }"
onSelected="() => this.onNavBarDropdownItemSelection(subItem)"
/>
</t>
</t>
@@ -145,11 +149,12 @@
<t t-foreach="sections" t-as="section" t-key="section.id">
<t t-if="!section.childrenTree.length">
<MenuItem
<DropdownItem
class="o_more_dropdown_section"
payload="section"
href="getMenuItemHref(section)"
t-esc="section.name"
dataset="{ menuXmlid: section.xmlid, section: section.id }"
onSelected="() => this.onNavBarDropdownItemSelection(section)"
/>
</t>
<t t-else="">
@@ -3,26 +3,12 @@
import { browser } from "@web/core/browser/browser";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
import { useEffect, useService } from "@web/core/utils/hooks";
import { useService } from "@web/core/utils/hooks";
const { Component } = owl;
const userMenuRegistry = registry.category("user_menuitems");
class UserMenuItem extends DropdownItem {
setup() {
super.setup();
useEffect(
() => {
if (this.props.payload.id) {
this.el.dataset.menu = this.props.payload.id;
}
},
() => []
);
}
}
export class UserMenu extends Component {
setup() {
this.user = useService("user");
@@ -42,13 +28,9 @@ export class UserMenu extends Component {
});
return sortedItems;
}
onDropdownItemSelected(ev) {
ev.detail.payload.callback();
}
}
UserMenu.template = "web.UserMenu";
UserMenu.components = { UserMenuItem };
UserMenu.components = { DropdownItem };
export const systrayItem = {
Component: UserMenu,
@@ -2,17 +2,18 @@
<templates xml:space="preserve">
<t t-name="web.UserMenu" owl="1">
<Dropdown class="o_user_menu" t-on-dropdown-item-selected.stop="onDropdownItemSelected">
<Dropdown class="o_user_menu">
<t t-set-slot="toggler">
<img class="rounded-circle o_user_avatar" t-att-src="source" alt="User"/> <span class="oe_topbar_name"><t t-esc="user.name"/><t t-if="env.debug" t-esc="' (' + user.db.name + ')'"/></span>
</t>
<t t-foreach="getElements()" t-as="element" t-key="element_index">
<t t-if="!element.hide">
<UserMenuItem
<DropdownItem
t-if="element.type == 'item'"
payload="{ callback: element.callback, id: element.id }"
href="element.href"
t-esc="element.description"
dataset="{ menu: element.id }"
onSelected="element.callback"
/>
<div t-if="element.type == 'separator'" role="separator" class="dropdown-divider"/>
</t>
+19 -44
View File
@@ -192,13 +192,13 @@ QUnit.module("Components", ({ beforeEach }) => {
QUnit.test("payload received on item selection", async (assert) => {
class Parent extends owl.Component {
onItemSelected(ev) {
assert.deepEqual(ev.detail.payload, { answer: 42 });
onItemSelected(value) {
assert.equal(value, 42);
}
}
Parent.template = owl.tags.xml`
<Dropdown t-on-dropdown-item-selected="onItemSelected">
<DropdownItem payload="{ answer: 42 }"/>
<Dropdown>
<DropdownItem onSelected="() => onItemSelected(42)"/>
</Dropdown>
`;
env = await makeTestEnv();
@@ -323,29 +323,6 @@ QUnit.module("Components", ({ beforeEach }) => {
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("multi-level dropdown: payload bubbles on item selection", async (assert) => {
assert.expect(2);
class Parent extends owl.Component {
onItemSelected(ev) {
assert.deepEqual(ev.detail.payload, { answer: 42 });
}
}
Parent.template = owl.tags.xml`
<Dropdown t-on-dropdown-item-selected="onItemSelected">
<Dropdown t-on-dropdown-item-selected="onItemSelected">
<DropdownItem payload="{ answer: 42 }" />
</Dropdown>
</Dropdown>
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
// As two listeners are defined in the template,
// clicking once the item would execute the handler twice.
await click(parent.el, ".dropdown-menu > .dropdown-item");
});
QUnit.test("multi-level dropdown: recursive template can be rendered", async (assert) => {
const recursiveTemplate = `
<Dropdown startOpen="true">
@@ -572,16 +549,15 @@ QUnit.module("Components", ({ beforeEach }) => {
QUnit.test("dropdowns keynav", async (assert) => {
assert.expect(26);
class Parent extends owl.Component {
onItemSelected(ev) {
const { payload } = ev.detail;
assert.step(payload.val.toString());
onItemSelected(value) {
assert.step(value.toString());
}
}
Parent.template = owl.tags.xml`
<Dropdown hotkey="'m'" t-on-dropdown-item-selected="onItemSelected">
<DropdownItem class="item1" payload="{val:1}">item1</DropdownItem>
<DropdownItem class="item2" hotkey="'2'" payload="{val:2}">item2</DropdownItem>
<DropdownItem class="item3" payload="{val:3}">item3</DropdownItem>
<Dropdown hotkey="'m'">
<DropdownItem class="item1" onSelected="() => onItemSelected(1)">item1</DropdownItem>
<DropdownItem class="item2" hotkey="'2'" onSelected="() => onItemSelected(2)">item2</DropdownItem>
<DropdownItem class="item3" onSelected="() => onItemSelected(3)">item3</DropdownItem>
</Dropdown>
`;
env = await makeTestEnv();
@@ -689,23 +665,22 @@ QUnit.module("Components", ({ beforeEach }) => {
QUnit.test("multi-level dropdown: keynav", async (assert) => {
assert.expect(125);
class Parent extends owl.Component {
onItemSelected(ev) {
const { payload } = ev.detail;
assert.step(payload.val);
onItemSelected(value) {
assert.step(value);
}
}
Parent.template = owl.tags.xml`
<Dropdown class="first" hotkey="'1'" t-on-dropdown-item-selected="onItemSelected">
<DropdownItem class="first-first" payload="{val:'first-first'}">O</DropdownItem>
<Dropdown class="first" hotkey="'1'">
<DropdownItem class="first-first" onSelected="() => onItemSelected('first-first')">O</DropdownItem>
<Dropdown class="second">
<DropdownItem class="second-first" payload="{val:'second-first'}">O</DropdownItem>
<DropdownItem class="second-first" onSelected="() => onItemSelected('second-first')">O</DropdownItem>
<Dropdown class="third">
<DropdownItem class="third-first" payload="{val:'third-first'}">O</DropdownItem>
<DropdownItem class="third-last" payload="{val:'third-last'}">O</DropdownItem>
<DropdownItem class="third-first" onSelected="() => onItemSelected('third-first')">O</DropdownItem>
<DropdownItem class="third-last" onSelected="() => onItemSelected('third-last')">O</DropdownItem>
</Dropdown>
<DropdownItem class="second-last" payload="{val:'second-last'}">O</DropdownItem>
<DropdownItem class="second-last" onSelected="() => onItemSelected('second-last')">O</DropdownItem>
</Dropdown>
<DropdownItem class="first-last" payload="{val:'first-last'}">O</DropdownItem>
<DropdownItem class="first-last" onSelected="() => onItemSelected('first-last')">O</DropdownItem>
</Dropdown>
`;
env = await makeTestEnv();