[REF] web: adapt ControlPanel to support new owl views
This commit contains several adaptations of the Layout, WithSearch, ControlPanel and its children components with respect to the conversion of kanban, list and form views in owl. In a nutshell: - ActionMenus: we didn't have an implementation yet, as already converted views do not need it. - ControlPanel: allow to move the focus from the search bar to the view and vice-versa + slight rework of the template to properly handle hidden areas. - CustomFilterItems: adapt to formatter/parser changes. - Layout: hide breadcrumbs in dialogs and allow to move control panel buttons in the footer (in dialogs as well) - WithSearch: only pass search props to the underlying view + take its content in a default slot Part-of: odoo/odoo#92475 Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Bruno Boi <boi@odoo.com> Co-authored-by: Géry Debongnie <ged@odoo.com> Co-authored-by: Samuel Degueldre <sad@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: Simon Genin (ges) <ges@odoo.com> Co-authored-by: Francois (fge) <fge@odoo.com> Co-authored-by: Michael Mattiello (mcm) <mcm@odoo.com> Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: Lucas Perais (lpe) <lpe@odoo.com> Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com> Co-authored-by: luvi <luvi@odoo.com>
This commit is contained in:
co-authored by
Bruno Boi
Géry Debongnie
Samuel Degueldre
Mathieu Duckerts-Antoine
Simon Genin
Francois
Michael Mattiello
Julien Mougenot
Lucas Perais
Jorge Pinna Puissant
luvi
parent
af0f323bac
commit
b0f20bcd09
@@ -50,7 +50,7 @@ export class Pager extends Component {
|
||||
return parts.join("-");
|
||||
}
|
||||
/**
|
||||
* @returns {boolean} true iff there is only one page
|
||||
* @returns {boolean} true if there is only one page
|
||||
*/
|
||||
get isSinglePage() {
|
||||
return this.minimum === 1 && this.maximum === this.props.total;
|
||||
@@ -68,15 +68,14 @@ export class Pager extends Component {
|
||||
} else if (minimum < 0 && this.props.limit > 1) {
|
||||
minimum = this.props.total - (this.props.total % this.props.limit || this.props.limit);
|
||||
}
|
||||
|
||||
this.update(minimum, this.props.limit);
|
||||
this.update(minimum, this.props.limit, true);
|
||||
}
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {{ minimum: number, maximum: number }}
|
||||
*/
|
||||
parse(value) {
|
||||
let [minimum, maximum] = value.trim().split(/\s*[\-\s,;]\s*/);
|
||||
let [minimum, maximum] = value.trim().split(/\s*[-\s,;]\s*/);
|
||||
const clamp = (value) => Math.min(Math.max(value, 1), this.props.total);
|
||||
return {
|
||||
minimum: clamp(parseInt(minimum, 10)) - 1,
|
||||
@@ -96,10 +95,11 @@ export class Pager extends Component {
|
||||
/**
|
||||
* @param {number} offset
|
||||
* @param {number} limit
|
||||
* @param {Boolean} hasNavigated
|
||||
*/
|
||||
async update(offset, limit) {
|
||||
async update(offset, limit, hasNavigated) {
|
||||
this.state.isDisabled = true;
|
||||
await this.props.onUpdate({ offset, limit });
|
||||
await this.props.onUpdate({ offset, limit }, hasNavigated);
|
||||
this.state.isDisabled = false;
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { session } from "@web/session";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, onWillStart, onWillUpdateProps } = owl;
|
||||
let registryActionId = 0;
|
||||
/**
|
||||
* Action menus (or Action/Print bar, previously called 'Sidebar')
|
||||
*
|
||||
* The side bar is the group of dropdown menus located on the left side of the
|
||||
* control panel. Its role is to display a list of items depending on the view
|
||||
* type and selected records and to execute a set of actions on active records.
|
||||
* It is made out of 2 dropdown: Print and Action.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export class ActionMenus extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.actionService = useService("action");
|
||||
onWillStart(async () => {
|
||||
this.actionItems = await this.setActionItems(this.props);
|
||||
});
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
this.actionItems = await this.setActionItems(nextProps);
|
||||
});
|
||||
}
|
||||
|
||||
get printItems() {
|
||||
const printActions = this.props.items.print || [];
|
||||
return printActions.map((action) => ({
|
||||
action,
|
||||
description: action.name,
|
||||
key: action.id,
|
||||
}));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Private
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
async setActionItems(props) {
|
||||
// Callback based actions
|
||||
const callbackActions = (props.items.other || []).map((action) =>
|
||||
Object.assign({ key: `action-${action.description}` }, action)
|
||||
);
|
||||
// Action based actions
|
||||
const actionActions = props.items.action || [];
|
||||
const formattedActions = actionActions.map((action) => ({
|
||||
action,
|
||||
description: action.name,
|
||||
key: action.id,
|
||||
}));
|
||||
// ActionMenus action registry components
|
||||
const registryActions = [];
|
||||
for (const { Component, getProps } of registry.category("action_menus").getAll()) {
|
||||
const itemProps = await getProps(props, this.env);
|
||||
if (itemProps) {
|
||||
registryActions.push({
|
||||
Component,
|
||||
key: `registry-action-${registryActionId++}`,
|
||||
props: itemProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [...callbackActions, ...formattedActions, ...registryActions];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Handlers
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
async executeAction(action) {
|
||||
let activeIds = this.props.activeIds;
|
||||
if (this.props.isDomainSelected) {
|
||||
activeIds = await this.orm.search(this.props.resModel, this.props.domain, {
|
||||
limit: session.active_ids_limit,
|
||||
context: this.props.context,
|
||||
});
|
||||
}
|
||||
const activeIdsContext = {
|
||||
active_id: activeIds[0],
|
||||
active_ids: activeIds,
|
||||
active_model: this.props.resModel,
|
||||
};
|
||||
if (this.props.domain) {
|
||||
// keep active_domain in context for backward compatibility
|
||||
// reasons, and to allow actions to bypass the active_ids_limit
|
||||
activeIdsContext.active_domain = this.props.domain;
|
||||
}
|
||||
const context = makeContext([this.props.context, activeIdsContext]);
|
||||
return this.actionService.doAction(action.id, {
|
||||
additionalContext: context,
|
||||
on_close: () => this.trigger("reload"),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler used to determine which way must be used to execute a selected
|
||||
* action: it will be either:
|
||||
* - a callback (function given by the view controller);
|
||||
* - an action ID (string);
|
||||
* - an URL (string).
|
||||
* @private
|
||||
* @param {Object} item
|
||||
*/
|
||||
onItemSelected(item) {
|
||||
if (item.callback) {
|
||||
item.callback([item]);
|
||||
} else if (item.action) {
|
||||
this.executeAction(item.action);
|
||||
} else if (item.url) {
|
||||
// Event has been prevented at its source: we need to redirect manually.
|
||||
browser.location = item.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionMenus.components = {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
};
|
||||
ActionMenus.props = {
|
||||
activeIds: { type: Array, element: [Number, String] }, // virtual IDs are strings.
|
||||
context: Object,
|
||||
resModel: String,
|
||||
domain: { type: Array, optional: true },
|
||||
isDomainSelected: { type: Boolean, optional: true },
|
||||
items: {
|
||||
type: Object,
|
||||
shape: {
|
||||
action: { type: Array, optional: true },
|
||||
print: { type: Array, optional: true },
|
||||
other: { type: Array, optional: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
ActionMenus.template = "web.ActionMenus";
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.ActionMenus" owl="1">
|
||||
<div class="o_cp_action_menus">
|
||||
<Dropdown t-if="printItems.length" togglerClass="'btn btn-secondary'" tooltip="'Printing options'" hotkey="'shift+u'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="small mr-1 fa fa-print"/>
|
||||
<span class="o_dropdown_title">Print</span>
|
||||
</t>
|
||||
<t t-foreach="printItems" t-as="item" t-key="item.key">
|
||||
<DropdownItem class="'o_menu_item'" onSelected="() => this.onItemSelected(item)">
|
||||
<t t-esc="item.description"/>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown t-if="actionItems.length" togglerClass="'btn btn-secondary'" tooltip="'Additional actions'" hotkey="'u'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="small mr-1 fa fa-cog"/>
|
||||
<span class="o_dropdown_title">Action</span>
|
||||
</t>
|
||||
<t t-foreach="actionItems" t-as="item" t-key="item.key">
|
||||
<t t-if="item.Component" t-component="item.Component" t-props="item.props" />
|
||||
<DropdownItem t-else="" class="'o_menu_item'" onSelected="() => this.onItemSelected(item)">
|
||||
<t t-esc="item.description"/>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ComparisonMenu } from "../comparison_menu/comparison_menu";
|
||||
@@ -25,6 +26,18 @@ export class ControlPanel extends Component {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
onBottomLeftKeydown(ev) {
|
||||
const hotkey = getActiveHotkey(ev);
|
||||
if (hotkey === "arrowdown") {
|
||||
this.env.searchModel.trigger("focus-view");
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object}
|
||||
*/
|
||||
@@ -34,6 +47,7 @@ export class ControlPanel extends Component {
|
||||
"top-left": true,
|
||||
"top-right": true,
|
||||
"bottom-left": true,
|
||||
"bottom-left-buttons": true,
|
||||
"bottom-right": true,
|
||||
},
|
||||
this.props.display || this.env.searchModel.display.controlPanel
|
||||
|
||||
@@ -2,46 +2,49 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.ControlPanel" owl="1">
|
||||
<div class="o_control_panel">
|
||||
<div class="o_control_panel" t-ref="root">
|
||||
<div t-if="display['top']" class="o_cp_top">
|
||||
<div t-if="display['top-left']" class="o_cp_top_left">
|
||||
<t t-slot="control-panel-top-left">
|
||||
<t t-call="web.Breadcrumbs" />
|
||||
<div class="o_cp_top_left">
|
||||
<t t-slot="control-panel-top-left" t-if="display['top-left']">
|
||||
<t t-call="web.Breadcrumbs" t-if="!env.config.noBreadcrumbs"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="display['top-right']" class="o_cp_top_right">
|
||||
<t t-slot="control-panel-top-right">
|
||||
<div class="o_cp_top_right">
|
||||
<t t-slot="control-panel-top-right" t-if="display['top-right']">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="display['bottom']" class="o_cp_bottom">
|
||||
<div t-if="display['bottom-left']" class="o_cp_bottom_left">
|
||||
<t t-slot="control-panel-bottom-left"/>
|
||||
<div class="o_cp_bottom_left" t-on-keydown="onBottomLeftKeydown">
|
||||
<t t-slot="control-panel-bottom-left-buttons" t-if="display['bottom-left'] and display['bottom-left-buttons']" />
|
||||
<t t-slot="control-panel-bottom-left" t-if="display['bottom-left']"/>
|
||||
</div>
|
||||
<div t-if="display['bottom-right']" class="o_cp_bottom_right">
|
||||
<t t-slot="control-panel-bottom-right">
|
||||
<div class="btn-group o_search_options position-static" role="search">
|
||||
<t t-foreach="searchMenus" t-as="menu" t-key="menu.key">
|
||||
<t t-component="menu.Component"/>
|
||||
</t>
|
||||
<div class="o_cp_bottom_right">
|
||||
<t t-if="display['bottom-right']">
|
||||
<t t-slot="control-panel-bottom-right">
|
||||
<div class="btn-group o_search_options position-static" role="search">
|
||||
<t t-foreach="searchMenus" t-as="menu" t-key="menu.key">
|
||||
<t t-component="menu.Component"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div class="o_cp_pager" role="search">
|
||||
<Pager t-if="pagerProps and pagerProps.total > 0" t-props="pagerProps"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div class="o_cp_pager" role="search">
|
||||
<Pager t-if="pagerProps and pagerProps.total > 0" t-props="pagerProps"/>
|
||||
</div>
|
||||
|
||||
<t t-if="env.config.viewSwitcherEntries.length">
|
||||
<nav class="btn-group o_cp_switch_buttons">
|
||||
<t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
|
||||
<button class="btn btn-light o_switch_view "
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
t-att-data-tooltip="view.name"
|
||||
t-on-click="() => this.onViewClicked(view.type)"
|
||||
/>
|
||||
</t>
|
||||
</nav>
|
||||
<t t-if="env.config.viewSwitcherEntries.length > 1">
|
||||
<nav class="btn-group o_cp_switch_buttons">
|
||||
<t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
|
||||
<button class="btn btn-light o_switch_view "
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
t-att-data-tooltip="view.name"
|
||||
t-on-click="() => this.onViewClicked(view.type)"
|
||||
/>
|
||||
</t>
|
||||
</nav>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,11 +55,7 @@
|
||||
<ol class="breadcrumb">
|
||||
<t t-foreach="env.config.breadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
|
||||
<t t-set="isPenultimate" t-value="breadcrumb_index === env.config.breadcrumbs.length - 2"/>
|
||||
<li t-if="!breadcrumb_last"
|
||||
class="breadcrumb-item"
|
||||
t-att-class="{ o_back_button: isPenultimate}"
|
||||
t-on-click.prevent="() => this.onBreadcrumbClicked(breadcrumb.jsId)"
|
||||
>
|
||||
<li t-if="!breadcrumb_last" class="breadcrumb-item" t-att-data-hotkey="isPenultimate and 'b'" t-att-class="{ o_back_button: isPenultimate}" t-on-click.prevent="() => this.onBreadcrumbClicked(breadcrumb.jsId)">
|
||||
<a href="#">
|
||||
<t t-if="breadcrumb.name" t-esc="breadcrumb.name"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DatePicker, DateTimePicker } from "@web/core/datepicker/datepicker";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { serializeDate, serializeDateTime } from "@web/core/l10n/dates";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
@@ -86,23 +86,23 @@ const FIELD_OPERATORS = {
|
||||
],
|
||||
};
|
||||
|
||||
const parseField = (field, value, opts = {}) => {
|
||||
function parseField(field, value, opts = {}) {
|
||||
if (FIELD_TYPES[field.type] === "char") {
|
||||
return value;
|
||||
}
|
||||
const type = field.type === "id" ? "integer" : field.type;
|
||||
const parse = parsers.contains(type) ? parsers.get(type) : (v) => v;
|
||||
return parse(value, { field, ...opts });
|
||||
};
|
||||
}
|
||||
|
||||
const formatField = (field, value, opts = {}) => {
|
||||
function formatField(field, value, opts = {}) {
|
||||
if (FIELD_TYPES[field.type] === "char") {
|
||||
return value;
|
||||
}
|
||||
const type = field.type === "id" ? "integer" : field.type;
|
||||
const format = formatters.contains(type) ? formatters.get(type) : (v) => v;
|
||||
return format(value, { field, ...opts });
|
||||
};
|
||||
return format(value, { digits: field.digits, ...opts });
|
||||
}
|
||||
|
||||
export class CustomFilterItem extends Component {
|
||||
setup() {
|
||||
@@ -176,7 +176,11 @@ export class CustomFilterItem extends Component {
|
||||
if (genericType === "datetime") {
|
||||
condition.value[0] = condition.value[0].set({ hour: 0, minute: 0, second: 0 });
|
||||
if (operator.symbol === "between") {
|
||||
condition.value[1] = condition.value[1].set({ hour: 23, minute: 59, second: 59 });
|
||||
condition.value[1] = condition.value[1].set({
|
||||
hour: 23,
|
||||
minute: 59,
|
||||
second: 59,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -9,22 +9,28 @@ const { Component } = owl;
|
||||
* @param {Object} params
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const extractLayoutComponents = (params) => {
|
||||
export function extractLayoutComponents(params) {
|
||||
return {
|
||||
ControlPanel: params.ControlPanel || ControlPanel,
|
||||
SearchPanel: params.SearchPanel || SearchPanel,
|
||||
Banner: params.Banner || false,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class Layout extends Component {
|
||||
setup() {
|
||||
const { display = {} } = this.env.searchModel || {};
|
||||
this.components = extractLayoutComponents(this.env.config);
|
||||
if (display.controlPanel && this.env.inDialog) {
|
||||
display.controlPanel["top-left"] = false;
|
||||
display.controlPanel["bottom-left-buttons"] = false;
|
||||
}
|
||||
this.display = display;
|
||||
}
|
||||
get controlPanelSlots() {
|
||||
const slots = { ...this.props.slots };
|
||||
slots["control-panel-bottom-left-buttons"] = slots["layout-buttons"];
|
||||
delete slots["layout-buttons"];
|
||||
delete slots.default;
|
||||
return slots;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.Layout" owl="1">
|
||||
<t t-if="env.inDialog" t-portal="'#' + env.dialogId + ' .modal-footer'">
|
||||
<t t-slot="layout-buttons"/>
|
||||
</t>
|
||||
<t t-component="components.ControlPanel" slots="controlPanelSlots" t-if="display.controlPanel"/>
|
||||
<div class="o_content" t-attf-class="{{props.className}}" t-att-class="{ o_component_with_search_panel: display.searchPanel }">
|
||||
<t t-component="components.Banner" t-if="components.Banner and display.banner"/>
|
||||
|
||||
@@ -23,7 +23,7 @@ const { useEnv, useChildSubEnv, useState, onWillRender } = owl;
|
||||
*/
|
||||
export function usePager(getProps) {
|
||||
const env = useEnv();
|
||||
const pagerState = useState(getProps());
|
||||
const pagerState = useState({});
|
||||
|
||||
useChildSubEnv({
|
||||
config: {
|
||||
@@ -32,6 +32,6 @@ export function usePager(getProps) {
|
||||
},
|
||||
});
|
||||
onWillRender(() => {
|
||||
Object.assign(pagerState, getProps());
|
||||
Object.assign(pagerState, getProps() || { total: 0 });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ const DEFAULT_VIEWS_WITH_SEARCH_PANEL = ["kanban", "list"];
|
||||
* @param {string} context
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const getContextGroubBy = (context) => {
|
||||
function getContextGroubBy(context) {
|
||||
try {
|
||||
return makeContext([context]).group_by.split(":");
|
||||
} catch (_err) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function reduceType(type) {
|
||||
if (type === "dateFilter") {
|
||||
|
||||
@@ -37,7 +37,7 @@ export class SearchBar extends Component {
|
||||
|
||||
this.inputRef = useAutofocus();
|
||||
|
||||
useBus(this.env.bus, "FOCUS-SEARCH-BAR", () => {
|
||||
useBus(this.env.searchModel, "focus-search", () => {
|
||||
this.inputRef.el.focus();
|
||||
});
|
||||
|
||||
@@ -345,7 +345,7 @@ export class SearchBar extends Component {
|
||||
focusedIndex = this.state.focusedIndex + 1;
|
||||
}
|
||||
} else {
|
||||
this.env.bus.trigger("FOCUS-VIEW");
|
||||
this.env.searchModel.trigger("focus-view");
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
|
||||
@@ -266,8 +266,10 @@ export class SearchModel extends EventBus {
|
||||
this.__legacyParseSearchPanelArchAnyway(searchViewDescription, searchViewFields);
|
||||
this.domainParts = {};
|
||||
this.display = this._getDisplay(config.display);
|
||||
|
||||
return this._reloadSections();
|
||||
if (!this.searchPanelInfo.loaded) {
|
||||
return this._reloadSections();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.blockNotification = true;
|
||||
@@ -310,7 +312,7 @@ export class SearchModel extends EventBus {
|
||||
);
|
||||
const { labels, preSearchItems, searchPanelInfo, sections } = parser.parse();
|
||||
|
||||
this.searchPanelInfo = { ...searchPanelInfo, shouldReload: false };
|
||||
this.searchPanelInfo = { ...searchPanelInfo, loaded: false, shouldReload: false };
|
||||
|
||||
await Promise.all(labels.map((cb) => cb(this.orm)));
|
||||
|
||||
@@ -1284,6 +1286,7 @@ export class SearchModel extends EventBus {
|
||||
async _fetchSections(categoriesToLoad, filtersToLoad) {
|
||||
await this._fetchCategories(categoriesToLoad);
|
||||
await this._fetchFilters(filtersToLoad);
|
||||
this.searchPanelInfo.loaded = true;
|
||||
}
|
||||
|
||||
_getActiveComparison() {
|
||||
@@ -1423,9 +1426,9 @@ export class SearchModel extends EventBus {
|
||||
let domain;
|
||||
try {
|
||||
domain = Domain.and(domains);
|
||||
return params.raw ? domain : domain.toList(
|
||||
Object.assign({}, this.globalContext, this.userService.context)
|
||||
);
|
||||
return params.raw
|
||||
? domain
|
||||
: domain.toList(Object.assign({}, this.globalContext, this.userService.context));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${this.env._t("Failed to evaluate the domain")} ${domain.toString()}.\n${
|
||||
@@ -2094,6 +2097,6 @@ export class SearchModel extends EventBus {
|
||||
const parser = new SearchArchParser(searchViewDescription, searchViewFields);
|
||||
const { searchPanelInfo } = parser.parse();
|
||||
|
||||
this.searchPanelInfo = { ...searchPanelInfo, shouldReload: false };
|
||||
this.searchPanelInfo = { ...searchPanelInfo, loaded: false, shouldReload: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ export const GROUPABLE_TYPES = [
|
||||
"datetime",
|
||||
"integer",
|
||||
"many2one",
|
||||
"many2many",
|
||||
"selection",
|
||||
];
|
||||
|
||||
@@ -7,12 +7,9 @@ import { CallbackRecorder, useSetupAction } from "@web/webclient/actions/action_
|
||||
const { Component, onWillStart, onWillUpdateProps, toRaw, useSubEnv } = owl;
|
||||
|
||||
export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"];
|
||||
const OTHER_SEARCH_KEYS = ["irFilters", "searchViewArch", "searchViewFields", "searchViewId"];
|
||||
|
||||
export class WithSearch extends Component {
|
||||
setup() {
|
||||
this.Component = this.props.Component;
|
||||
|
||||
if (!this.env.__getContext__) {
|
||||
useSubEnv({ __getContext__: new CallbackRecorder() });
|
||||
}
|
||||
@@ -54,31 +51,12 @@ export class WithSearch extends Component {
|
||||
await this.searchModel.reload(config);
|
||||
});
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Getters
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
get componentProps() {
|
||||
const componentProps = { ...this.props.componentProps };
|
||||
for (const key of SEARCH_KEYS) {
|
||||
componentProps[key] = this.searchModel[key];
|
||||
}
|
||||
componentProps.info = componentProps.info || {};
|
||||
for (const key of OTHER_SEARCH_KEYS) {
|
||||
componentProps.info[key] = this.searchModel[key];
|
||||
}
|
||||
return componentProps;
|
||||
}
|
||||
}
|
||||
|
||||
WithSearch.defaultProps = {
|
||||
componentProps: {},
|
||||
};
|
||||
WithSearch.template = "web.WithSearch";
|
||||
WithSearch.props = {
|
||||
Component: Function,
|
||||
slots: Object,
|
||||
SearchModel: { type: Function, optional: true },
|
||||
componentProps: { type: Object, optional: true },
|
||||
|
||||
resModel: String,
|
||||
|
||||
@@ -107,4 +85,3 @@ WithSearch.props = {
|
||||
dynamicFilters: { type: Array, element: Object, optional: true },
|
||||
searchMenuTypes: { type: Array, element: String, optional: true },
|
||||
};
|
||||
WithSearch.template = "web.WithSearch";
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.WithSearch" owl="1">
|
||||
<t t-component="Component" t-props="componentProps" />
|
||||
<t t-slot="default"
|
||||
context="searchModel.context"
|
||||
domain="searchModel.domain"
|
||||
groupBy="searchModel.groupBy"
|
||||
orderBy="searchModel.orderBy"
|
||||
comparison="searchModel.comparison"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -322,18 +322,23 @@ QUnit.module("Search", (hooks) => {
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
this.searchState = useState({
|
||||
resModel: "animal",
|
||||
Component: TestComponent,
|
||||
domain: [["type", "=", "carnivorous"]],
|
||||
});
|
||||
}
|
||||
}
|
||||
Parent.template = xml`<WithSearch t-props="state"/>`;
|
||||
Parent.components = { WithSearch };
|
||||
Parent.template = xml`
|
||||
<WithSearch t-props="searchState" t-slot-scope="search">
|
||||
<TestComponent
|
||||
domain="search.domain"
|
||||
/>
|
||||
</WithSearch>
|
||||
`;
|
||||
Parent.components = { WithSearch, TestComponent };
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
parent.state.domain = [["type", "=", "herbivorous"]];
|
||||
parent.searchState.domain = [["type", "=", "herbivorous"]];
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user