fields
forms
Properties
Odoo 19 fields — Properties (views)
Live preview
Interactive
Source excerpt
web/static/src/views/fields/properties/properties_field.js
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { _t } from "@web/core/l10n/translation";
import { usePopover } from "@web/core/popover/popover_hook";
import { reposition } from "@web/core/position/utils";
import { registry } from "@web/core/registry";
import { user } from "@web/core/user";
import { useBus, useService } from "@web/core/utils/hooks";
import { useSortable } from "@web/core/utils/sortable_owl";
import { exprToBoolean, uuid } from "@web/core/utils/strings";
import { useRecordObserver } from "@web/model/relational_model/utils";
import { standardFieldProps } from "../standard_field_props";
import { PropertyDefinition } from "./property_definition";
import { PropertyValue } from "./property_value";
import { Component, onWillStart, onWillUpdateProps, useEffect, useRef, useState } from "@odoo/owl";
export class PropertiesField extends Component {
static template = "web.PropertiesField";
static components = {
Dropdown,
DropdownItem,
PropertyDefinition,
PropertyValue,
};
static props = {
...standardFieldProps,
context: { type: Object, optional: true },
columns: {
type: Number,
optional: true,
validate: (columns) => [1, 2].includes(columns),
},
editMode: { type: Boolean, optional: true },
};
setup() {
this.notification = useService("notification");
this.orm = useService("orm");
this.dialogService = useService("dialog");
this.popover = usePopover(PropertyDefinition, {
closeOnClickAway: this.checkPopoverClose,
popoverClass: "o_property_field_popover",
position: "right",
onClose: () => this.onCloseCurrentPopover?.(),
fixedPosition: true,
arrow: false,
setActiveElement: false, // make tag navigation work when adding a tag property
});
this.propertiesRef = useRef("properties");
let currentResId;
useRecordObserver((record) => {
if (currentResId !== record.resId) {
currentResId = record.resId;
this._saveInitialPropertiesValues();
}
});
const field = this.props.record.fields[this.props.name];
this.definitionRecordField = field.definition_record;
this.state = useState({
canChangeDefinition: false,
isInEditMode: false,
movedPropertyName: null,
});
// Properties can be added from the cog menu of the form controller
if (this.env.config?.viewType === "form") {
useBus(this.env.model.bus, "PROPERTY_FIELD:EDIT", async () => {
if (this.props.readonly || this.state.isInEditMode) {
return;
}
let canChangeDefinition = this.state.canChangeDefinition;
if (!canChangeDefinition) {
canChangeDefinition = await this.checkDefinitionWriteAccess();
if (!canChangeDefinition) {
this.notification.add(this._getPropertyEditWarningText(), {
type: "warning",
});
}
}
const isInEditMode = canChangeDefinition && !this.props.readonly;
this.state.canChangeDefinition = !!canChangeDefinition;
this.state.isInEditMode = isInEditMode;
if (isInEditMode && this.propertiesList.length === 0) {
this.onPropertyCreate();
}
});
}
onWillStart(async () => {
if (this.props.readonly || !this.props.editMode) {
return;
}
this.checkDefinitionWriteAccess().then((canChangeDefinition) => {
if (canChangeDefinition) {
this.state.canChangeDefinition = true;
this.state.isInEditMode = !this.props.readonly;
}
});
});
useEffect(
() => {
// when the field has a new definition record:
if (this.props.readonly || (!this.state.isInEditMode && !this.props.editMode)) {
return;
}
this.checkDefinitionWriteAccess().then((canChangeDefinition) => {
this.state.canChangeDefinition = !!canChangeDefinition;
this.state.isInEditMode =
canChangeDefinition &&
!this.props.readonly &&
(this.state.isInEditMode || this.props.editMode);
});
},
() => [this.props.record.data[this.definitionRecordField]]
Registry / API
- Registry name
properties- Category
fields- Module
web- Slug
properties- Nav group
forms