[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:
@@ -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()">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user