[REF] *: do not use activeFields in Field components

This commit is to replace all uses of activeFields in Field components
by passing fieldInfos to extractProps. We will therefore retrieve the
node-dependent information in the extractProps function.

Why?
This ensures that the info used is that of the correct node. Because the
activeFields function contains the information from the last node that
referred to the same field.

Example:
<tree>
    <field name="a"/>
    <field name="a" context='{'b':"yop"}'/>
</tree>

For the first field:
    FieldInfo.context = {}
    activeField.context = {'b':"yop"}

For the second field:
    FieldInfo.context =  {'b':"yop"}
    activeField.context = {'b':"yop"}

Part of task: 3179751

closes odoo/odoo#113874

Related: odoo/enterprise#37621
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2023-03-06 15:18:48 +01:00
parent 3620ae22ba
commit 17a24020ac
24 changed files with 199 additions and 136 deletions
@@ -16,10 +16,7 @@ export function useAssignUserCommand() {
const orm = useService("orm");
const user = useService("user");
const type = component.props.record.fields[component.props.name].type;
if (
component.relation !== "res.users" ||
component.props.record.activeFields[component.props.name].viewType !== "form"
) {
if (component.relation !== "res.users") {
return;
}
@@ -18,7 +18,9 @@ export class Many2ManyTagsAvatarUserField extends Many2ManyTagsAvatarField {
setup() {
super.setup();
this.openChat = useOpenChat(this.relation);
useAssignUserCommand();
if (this.props.withCommand) {
useAssignUserCommand();
}
}
get tags() {
@@ -44,15 +46,24 @@ export const many2ManyTagsAvatarUserField = {
registry.category("fields").add("many2many_avatar_user", many2ManyTagsAvatarUserField);
export class KanbanMany2ManyTagsAvatarUserField extends ListKanbanMany2ManyTagsAvatarField {
static props = {
...ListKanbanMany2ManyTagsAvatarField.props,
displayText: { type: Boolean, optional: true },
};
setup() {
super.setup();
this.openChat = useOpenChat(this.relation);
useAssignUserCommand();
if (this.props.withCommand) {
useAssignUserCommand();
}
}
get displayText() {
const isList = this.props.record.activeFields[this.props.name].viewType === "list";
return (isList && this.props.value.records.length === 1) || !this.props.readonly;
return (
(this.props.displayText && this.props.value.records.length === 1) ||
!this.props.readonly
);
}
get tags() {
@@ -75,6 +86,10 @@ export const kanbanMany2ManyTagsAvatarUserField = {
...listKanbanMany2ManyTagsAvatarField,
component: KanbanMany2ManyTagsAvatarUserField,
additionalClasses: ["o_field_many2many_tags_avatar"],
extractProps: (fieldInfo) => ({
...listKanbanMany2ManyTagsAvatarField.extractProps(fieldInfo),
displayText: fieldInfo.viewType === "list",
}),
};
registry.category("fields").add("kanban.many2many_avatar_user", kanbanMany2ManyTagsAvatarUserField);
@@ -9,11 +9,18 @@ import { useOpenChat } from "@mail/views/open_chat_hook";
import { useAssignUserCommand } from "@mail/views/fields/assign_user_command_hook";
export class Many2OneAvatarUserField extends Many2OneAvatarField {
static props = {
...Many2OneAvatarField.props,
withCommand: { type: Boolean, optional: true },
};
setup() {
super.setup();
const relation = this.props.record.fields[this.props.name].relation;
this.openChat = useOpenChat(relation);
useAssignUserCommand();
if (this.props.withCommand) {
useAssignUserCommand();
}
}
onClickAvatar() {
@@ -26,21 +33,15 @@ export const many2OneAvatarUserField = {
...many2OneAvatarField,
component: Many2OneAvatarUserField,
additionalClasses: ["o_field_many2one_avatar"],
extractProps: (fieldInfo) => ({
...many2OneAvatarField.extractProps(fieldInfo),
withCommand: fieldInfo.viewType === "form",
}),
};
registry.category("fields").add("many2one_avatar_user", many2OneAvatarUserField);
export class KanbanMany2OneAvatarUserField extends Many2OneAvatarUserField {
/**
* All props are normally passed to the Many2OneField however since
* we add a new one, we need to filter it out.
*/
get m2oFieldProps() {
return Object.fromEntries(
Object.entries(this.props).filter(([key, _val]) => key in Many2OneAvatarField.props)
);
}
}
export class KanbanMany2OneAvatarUserField extends Many2OneAvatarUserField {}
KanbanMany2OneAvatarUserField.template = "mail.KanbanMany2OneAvatarUserField";
KanbanMany2OneAvatarUserField.props = {
...Many2OneAvatarUserField.props,
@@ -9,7 +9,6 @@
<t t-name="mail.KanbanMany2OneAvatarUserField" t-inherit="mail.Many2OneAvatarUserField" t-inherit-mode="primary" owl="1">
<Many2OneField position="attributes">
<attribute name="t-if">props.readonly and props.displayAvatarName</attribute>
<attribute name="t-props">m2oFieldProps</attribute>
</Many2OneField>
</t>
</templates>
@@ -15,13 +15,6 @@ export class ProjectStateSelectionField extends StateSelectionField {
this.colors = STATUS_COLORS;
}
/**
* @override
*/
get showLabel() {
return !this.props.hideLabel;
}
/**
* @override
*/
@@ -33,6 +26,10 @@ export class ProjectStateSelectionField extends StateSelectionField {
export const projectStateSelectionField = {
...stateSelectionField,
component: ProjectStateSelectionField,
extractProps: (fieldInfo) => ({
...stateSelectionField.extractProps(fieldInfo),
hideLabel: !!fieldInfo.options.hideLabel,
}),
};
registry.category("fields").add("kanban.project_state_selection", projectStateSelectionField);
@@ -3,35 +3,28 @@
import { sprintf } from "@web/core/utils/strings";
import { registry } from "@web/core/registry";
import { PriorityField, priorityField } from "@web/views/fields/priority/priority_field";
import { useCommand } from "@web/core/commands/command_hook";
import { useState } from "@odoo/owl";
export class PrioritySwitchField extends PriorityField {
setup() {
this.state = useState({
index: -1,
});
if (this.props.record.activeFields[this.props.name].viewType !== "form") {
return;
}
for (const [id, name] of this.options) {
useCommand(
sprintf(this.env._t("Set priority as %s"), name),
() => this.props.update(id),
{
category: "smart_action",
hotkey: "alt+r",
isAvailable: () => this.props.value !== id,
}
);
}
get commands() {
return this.options.map(([id, name]) => [
sprintf(this.env._t("Set priority as %s"), name),
() => this.updateRecord(id),
{
category: "smart_action",
hotkey: "alt+r",
isAvailable: () => this.props.value !== id,
},
]);
}
}
export const prioritySwitchField = {
...priorityField,
component: PrioritySwitchField,
extractProps: (fieldInfo) => ({
...priorityField.extractProps(fieldInfo),
withCommand: fieldInfo.viewType === "form",
}),
};
registry.category("fields").add("priority_switch", prioritySwitchField);
@@ -16,11 +16,13 @@ FieldMany2ManyAltPOsRenderer.recordRowTemplate = "purchase_requisition.AltPOsLis
export class FieldMany2ManyAltPOs extends X2ManyField {
setup() {
super.setup();
this.orm = useService("orm");
this.action = useService("action");
// TODO: this is a terrible hack, make this a proper extension of many2many if/when possible
this.props.record.activeFields[this.props.name].widget = "many2many";
super.setup();
}
get isMany2Many() {
return true;
}
/**
@@ -10,15 +10,12 @@ export class BooleanIconField extends Component {
static props = {
...standardFieldProps,
icon: { type: String, optional: true },
label: { type: String, optional: true },
};
static defaultProps = {
icon: "fa-check-square-o",
};
get label() {
return this.props.record.activeFields[this.props.name].string;
}
update() {
this.props.record.update({ [this.props.name]: !this.props.value });
}
@@ -28,8 +25,9 @@ export const booleanIconField = {
component: BooleanIconField,
displayName: _lt("Boolean Icon"),
supportedTypes: ["boolean"],
extractProps: ({ options }) => ({
extractProps: ({ options, string }) => ({
icon: options.icon,
label: string,
}),
};
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.BooleanIconField" owl="1">
<button t-attf-class="btn btn-sm btn-{{props.value ? 'primary' : 'outline-secondary'}} mx-2 py-1 fa {{props.icon}}" t-att-data-tooltip="label" t-on-click.prevent.stop="update" />
<button t-attf-class="btn btn-sm btn-{{props.value ? 'primary' : 'outline-secondary'}} mx-2 py-1 fa {{props.icon}}" t-att-data-tooltip="props.label" t-on-click.prevent.stop="update" />
</t>
</templates>
@@ -13,16 +13,13 @@ export class ColorPickerField extends Component {
};
static props = {
...standardFieldProps,
canToggle: { type: Boolean },
};
static RECORD_COLORS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
get canToggle() {
return this.props.record.activeFields[this.props.name].viewType !== "list";
}
get isExpanded() {
return !this.canToggle && !this.props.readonly;
return !this.props.canToggle && !this.props.readonly;
}
switchColor(colorIndex) {
@@ -33,6 +30,9 @@ export class ColorPickerField extends Component {
export const colorPickerField = {
component: ColorPickerField,
supportedTypes: ["integer"],
extractProps: ({ viewType }) => ({
canToggle: viewType !== "list",
}),
};
registry.category("fields").add("color_picker", colorPickerField);
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.ColorPickerField" owl="1">
<ColorList canToggle="canToggle" colors="constructor.RECORD_COLORS" forceExpanded="isExpanded" onColorSelected.bind="switchColor" selectedColor="props.value || 0"/>
<ColorList canToggle="props.canToggle" colors="constructor.RECORD_COLORS" forceExpanded="isExpanded" onColorSelected.bind="switchColor" selectedColor="props.value || 0"/>
</t>
</templates>
@@ -41,6 +41,7 @@ export class Many2ManyTagsField extends Component {
createDomain: { type: [Array, Boolean], optional: true },
placeholder: { type: String, optional: true },
nameCreateField: { type: String, optional: true },
string: { type: String, optional: true },
};
static defaultProps = {
canCreate: true,
@@ -112,7 +113,7 @@ export class Many2ManyTagsField extends Component {
return this.props.record.evalContext;
}
get string() {
return this.props.record.activeFields[this.props.name].string;
return this.props.string || this.props.record.fields[this.props.name].string || "";
}
getTagProps(record) {
@@ -257,7 +258,7 @@ export const many2ManyTagsField = {
}
return relatedFields;
},
extractProps: ({ attrs, options }) => {
extractProps: ({ attrs, options, string }) => {
const noCreate = Boolean(options.no_create);
const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate;
const noQuickCreate = Boolean(options.no_quick_create);
@@ -270,6 +271,7 @@ export const many2ManyTagsField = {
canCreateEdit: canCreate && !noCreateEdit,
createDomain: options.create,
placeholder: attrs.placeholder,
string,
};
},
};
@@ -14,6 +14,10 @@ export class Many2ManyTagsAvatarField extends Many2ManyTagsField {
Many2XAutocomplete,
TagsList,
};
static props = {
...Many2ManyTagsField.props,
withCommand: { type: Boolean, optional: true },
};
get tags() {
return super.tags.map((tag) => ({
@@ -27,14 +31,23 @@ export class Many2ManyTagsAvatarField extends Many2ManyTagsField {
export const many2ManyTagsAvatarField = {
...many2ManyTagsField,
component: Many2ManyTagsAvatarField,
extractProps: (fieldInfo) => ({
...many2ManyTagsField.extractProps(fieldInfo),
withCommand: fieldInfo.viewType === "form",
}),
};
registry.category("fields").add("many2many_tags_avatar", many2ManyTagsAvatarField);
export class ListKanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField {
get itemsVisible() {
return this.props.record.activeFields[this.props.name].viewType === "list" ? 5 : 3;
}
static props = {
...Many2ManyTagsAvatarField.props,
itemsVisible: { type: Number, optional: true },
};
static defaultProps = {
...Many2ManyTagsAvatarField.defaultProps,
itemsVisible: 3,
};
getTagProps(record) {
return {
@@ -47,6 +60,10 @@ export class ListKanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField
export const listKanbanMany2ManyTagsAvatarField = {
...many2ManyTagsAvatarField,
component: ListKanbanMany2ManyTagsAvatarField,
extractProps: (fieldInfo) => ({
...many2ManyTagsAvatarField.extractProps(fieldInfo),
itemsVisible: fieldInfo.viewType === "list" ? 5 : 3,
}),
};
registry.category("fields").add("list.many2many_tags_avatar", listKanbanMany2ManyTagsAvatarField);
@@ -6,7 +6,7 @@
class="o_field_tags d-inline-flex flex-wrap mw-100"
t-att-class="{'o_tags_input o_input': !props.readonly}"
>
<TagsList tags="tags" itemsVisible="itemsVisible"/>
<TagsList tags="tags" itemsVisible="props.itemsVisible"/>
<div t-if="showM2OSelectionField" class="o_field_many2many_selection d-inline-flex w-100" t-on-keydown="onAutoCompleteKeydown" t-ref="autoComplete">
<Many2XAutocomplete
id="props.id"
@@ -17,6 +17,13 @@ export class Many2OneAvatarField extends Component {
get relation() {
return this.props.relation || this.props.record.fields[this.props.name].relation;
}
get many2OneProps() {
return Object.fromEntries(
Object.entries(this.props).filter(
([key, _val]) => key in this.constructor.components.Many2OneField.props
)
);
}
}
export const many2OneAvatarField = {
@@ -7,7 +7,7 @@
<span t-if="props.value === false and !props.readonly" class="o_m2o_avatar_empty"></span>
<img t-if="props.value !== false" t-attf-src="/web/image/{{relation}}/{{props.value[0]}}/avatar_128" />
</span>
<Many2OneField t-props="props" canOpen="!props.readonly"/>
<Many2OneField t-props="many2OneProps" canOpen="!props.readonly"/>
</div>
</t>
@@ -11,15 +11,24 @@ export class PriorityField extends Component {
static template = "web.PriorityField";
static props = {
...standardFieldProps,
withCommand: { type: Boolean, optional: true },
};
setup() {
this.state = useState({
index: -1,
});
if (this.props.record.activeFields[this.props.name].viewType === "form") {
const commandName = this.env._t("Set priority...");
useCommand(
if (this.props.withCommand) {
for (const command of this.commands) {
useCommand(...command);
}
}
}
get commands() {
const commandName = this.env._t("Set priority...");
return [
[
commandName,
() => {
return {
@@ -37,9 +46,9 @@ export class PriorityField extends Component {
],
};
},
{ category: "smart_action", hotkey: "alt+r" }
);
}
{ category: "smart_action", hotkey: "alt+r" },
],
];
}
get tooltipLabel() {
@@ -91,6 +100,9 @@ export const priorityField = {
component: PriorityField,
displayName: _lt("Priority"),
supportedTypes: ["selection"],
extractProps: ({ viewType }) => ({
withCommand: viewType === "form",
}),
};
registry.category("fields").add("priority", priorityField);
@@ -11,6 +11,7 @@ export class RadioField extends Component {
static props = {
...standardFieldProps,
orientation: { type: String, optional: true },
label: { type: String, optional: true },
};
static defaultProps = {
orientation: "vertical",
@@ -38,9 +39,6 @@ export class RadioField extends Component {
get items() {
return RadioField.getItems(this.props.name, this.props.record);
}
get string() {
return this.props.record.activeFields[this.props.name].string;
}
get value() {
switch (this.props.record.fields[this.props.name].type) {
case "selection":
@@ -72,8 +70,9 @@ export const radioField = {
displayName: _lt("Radio"),
supportedTypes: ["many2one", "selection"],
isEmpty: (record, fieldName) => record.data[fieldName] === false,
extractProps: ({ options }) => ({
extractProps: ({ options, string }) => ({
orientation: options.horizontal ? "horizontal" : "vertical",
label: string,
}),
legacySpecialData: "_fetchSpecialMany2ones",
};
@@ -5,7 +5,7 @@
<div
role="radiogroup"
t-attf-class="o_{{ props.orientation }}"
t-att-aria-label="string"
t-att-aria-label="props.label"
>
<t t-foreach="items" t-as="item" t-key="item[0]">
<div class="form-check o_radio_item" aria-atomic="true">
@@ -15,6 +15,7 @@ export class StatInfoField extends Component {
labelField: { type: String, optional: true },
noLabel: { type: Boolean, optional: true },
digits: { type: Array, optional: true },
string: { type: String, optional: true },
};
get digits() {
@@ -28,7 +29,7 @@ export class StatInfoField extends Component {
get label() {
return this.props.labelField
? this.props.record.data[this.props.labelField]
: this.props.record.activeFields[this.props.name].string;
: this.props.string;
}
}
@@ -37,7 +38,7 @@ export const statInfoField = {
displayName: _lt("Stat Info"),
supportedTypes: ["float", "integer", "monetary"],
isEmpty: () => false,
extractProps: ({ attrs, options }) => {
extractProps: ({ attrs, options, string }) => {
// Sadly, digits param was available as an option and an attr.
// The option version could be removed with some xml refactoring.
let digits;
@@ -51,6 +52,7 @@ export const statInfoField = {
digits,
labelField: options.label_field,
noLabel: archParseBoolean(attrs.nolabel),
string,
};
},
};
@@ -19,6 +19,7 @@ export class StateSelectionField extends Component {
static props = {
...standardFieldProps,
hideLabel: { type: Boolean, optional: true },
withCommand: { type: Boolean, optional: true },
};
static defaultProps = {
hideLabel: false,
@@ -30,20 +31,19 @@ export class StateSelectionField extends Component {
blocked: "red",
done: "green",
};
if (this.props.record.activeFields[this.props.name].viewType !== "form") {
return;
}
const hotkeys = ["D", "F", "G"];
for (const [index, [value, label]] of this.options.entries()) {
useCommand(
sprintf(this.env._t("Set kanban state as %s"), label),
() => this.updateRecord(value),
{
category: "smart_action",
hotkey: "alt+" + hotkeys[index],
isAvailable: () => this.props.value !== value,
}
);
if (this.props.withCommand) {
const hotkeys = ["D", "F", "G"];
for (const [index, [value, label]] of this.options.entries()) {
useCommand(
sprintf(this.env._t("Set kanban state as %s"), label),
() => this.updateRecord(value),
{
category: "smart_action",
hotkey: "alt+" + hotkeys[index],
isAvailable: () => this.props.value !== value,
}
);
}
}
}
get options() {
@@ -61,10 +61,7 @@ export class StateSelectionField extends Component {
return formatSelection(this.currentValue, { selection: this.options });
}
get showLabel() {
return (
this.props.record.activeFields[this.props.name].viewType === "list" &&
!this.props.hideLabel
);
return !this.props.hideLabel;
}
get isReadonly() {
return this.props.record.isReadonly(this.props.name);
@@ -91,8 +88,9 @@ export const stateSelectionField = {
component: StateSelectionField,
displayName: _lt("Label Selection"),
supportedTypes: ["selection"],
extractProps: ({ options }) => ({
hideLabel: !!options.hide_label,
extractProps: ({ options, viewType }) => ({
hideLabel: !!options.hide_label && viewType === "list",
withCommand: viewType === "form",
}),
};
@@ -24,13 +24,14 @@ export class StatusBarField extends Component {
displayName: { type: String, optional: true },
isDisabled: { type: Boolean, optional: true },
visibleSelection: { type: Array, optional: true },
withCommand: { type: Boolean, optional: true },
};
static defaultProps = {
visibleSelection: [],
};
setup() {
if (this.props.record.activeFields[this.props.name].viewType === "form") {
if (this.props.withCommand) {
const commandName = sprintf(this.env._t(`Move to %s...`), escape(this.displayName));
useCommand(
commandName,
@@ -225,12 +226,13 @@ export const statusBarField = {
supportedTypes: ["many2one", "selection"],
isEmpty: (record, fieldName) => record.model.env.isSmall && !record.data[fieldName],
legacySpecialData: "_fetchSpecialStatus",
extractProps: ({ attrs, options }) => ({
extractProps: ({ attrs, options, viewType }) => ({
canCreate: Boolean(attrs.can_create),
canWrite: Boolean(attrs.can_write),
isDisabled: !options.clickable,
visibleSelection:
attrs.statusbar_visible && attrs.statusbar_visible.trim().split(/\s*,\s*/g),
withCommand: viewType === "form",
}),
};
@@ -27,31 +27,29 @@ export class X2ManyField extends Component {
...standardFieldProps,
addLabel: { type: String, optional: true },
editable: { type: String, optional: true },
viewMode: { type: String, optional: true },
widget: { type: String, optional: true },
crudOptions: { type: Object, optional: true },
string: { type: String, optional: true },
views: { type: Object, optional: true },
};
static defaultProps = {
archInfo: {},
};
setup() {
this.activeField = this.props.record.activeFields[this.props.name];
this.field = this.props.record.fields[this.props.name];
this.isMany2Many =
this.field.type === "many2many" || this.activeField.widget === "many2many";
this.viewMode = this.activeField.viewMode;
const { saveRecord, updateRecord, removeRecord } = useX2ManyCrud(
() => this.list,
this.isMany2Many
);
let archInfo;
if (this.viewMode) {
archInfo = this.activeField.views[this.viewMode];
} else {
archInfo = {};
}
if (this.viewMode === "kanban") {
this.creates = archInfo.creates.length
? archInfo.creates
this.archInfo = this.props.views[this.props.viewMode] || {};
const { activeActions, creates } = this.archInfo;
if (this.props.viewMode === "kanban") {
this.creates = creates.length
? creates
: [
{
type: "create",
@@ -60,9 +58,9 @@ export class X2ManyField extends Component {
},
];
}
const subViewActiveActions = archInfo.activeActions;
const subViewActiveActions = activeActions;
this.activeActions = useActiveActions({
crudOptions: Object.assign({}, this.activeField.options, {
crudOptions: Object.assign({}, this.props.crudOptions, {
onDelete: removeRecord,
}),
fieldType: this.isMany2Many ? "many2many" : "one2many",
@@ -86,7 +84,7 @@ export class X2ManyField extends Component {
getList: () => this.list,
saveRecord,
updateRecord,
withParentId: this.activeField.widget !== "many2many",
withParentId: this.props.widget !== "many2many",
});
this._openRecord = (params) => {
const activeElement = document.activeElement;
@@ -100,7 +98,9 @@ export class X2ManyField extends Component {
});
};
this.canOpenRecord =
this.viewMode === "list" ? !(archInfo.editable || this.props.editable) : true;
this.props.viewMode === "list"
? !(this.archInfo.editable || this.props.editable)
: true;
const selectCreate = useSelectCreate({
resModel: this.props.value.resModel,
@@ -117,14 +117,26 @@ export class X2ManyField extends Component {
};
}
get activeField() {
return {
views: this.props.views,
viewMode: this.props.viewMode,
string: this.props.string,
};
}
get displayControlPanelButtons() {
return (
this.viewMode === "kanban" &&
this.props.viewMode === "kanban" &&
("link" in this.activeActions ? this.activeActions.link : this.activeActions.create) &&
!this.props.readonly
);
}
get isMany2Many() {
return this.field.type === "many2many" || this.props.widget === "many2many";
}
get list() {
return this.props.value;
}
@@ -165,14 +177,14 @@ export class X2ManyField extends Component {
}
get rendererProps() {
const archInfo = this.activeField.views[this.viewMode];
const { archInfo } = this;
const props = {
archInfo,
list: this.list,
openRecord: this.openRecord.bind(this),
};
if (this.viewMode === "kanban") {
if (this.props.viewMode === "kanban") {
const recordsDraggable = !this.props.readonly && archInfo.recordsDraggable;
props.archInfo = { ...archInfo, recordsDraggable };
props.readonly = this.props.readonly;
@@ -227,7 +239,7 @@ export class X2ManyField extends Component {
const domain = record.getFieldDomain(this.props.name).toList();
context = makeContext([record.getFieldContext(this.props.name), context]);
if (this.isMany2Many) {
const { string } = this.props.record.activeFields[this.props.name];
const { string } = this.props;
const title = sprintf(this.env._t("Add: %s"), string);
return this.selectCreate({ domain, context, title });
}
@@ -258,9 +270,19 @@ export const x2ManyField = {
displayName: _lt("Relational table"),
supportedTypes: ["one2many", "many2many"],
useSubView: true,
extractProps: ({ attrs }) => ({
addLabel: attrs["add-label"],
}),
extractProps: ({ attrs, viewMode, views, widget, options, string }) => {
const props = {
addLabel: attrs["add-label"],
viewMode,
views,
crudOptions: options,
string,
};
if (widget) {
props.widget = widget;
}
return props;
},
};
registry.category("fields").add("one2many", x2ManyField);
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.X2ManyField" owl="1">
<div t-attf-class="o_field_x2many o_field_x2many_{{viewMode}}">
<div t-attf-class="o_field_x2many o_field_x2many_{{props.viewMode}}">
<div class="o_x2m_control_panel">
<t t-if="displayControlPanelButtons">
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
@@ -32,8 +32,8 @@
<Pager t-if="props.value.count > props.value.limit" t-props="pagerProps"/>
</div>
</div>
<ListRenderer t-if="viewMode === 'list'" t-props="rendererProps" />
<KanbanRenderer t-elif="viewMode" t-props="rendererProps" />
<ListRenderer t-if="props.viewMode === 'list'" t-props="rendererProps" />
<KanbanRenderer t-elif="props.viewMode" t-props="rendererProps" />
</div>
</t>