Skip to Content
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