Skip to Content
OWL data_display

Model Field Selector

Odoo 19 OWL component — Model Field Selector (core)

Live preview Interactive
Source excerpt web/static/src/core/model_field_selector/model_field_selector.js
import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
import { usePopover } from "@web/core/popover/popover_hook";
import { KeepLast } from "@web/core/utils/concurrency";
import { useService } from "@web/core/utils/hooks";
import { ModelFieldSelectorPopover } from "./model_field_selector_popover";

export class ModelFieldSelector extends Component {
    static template = "web._ModelFieldSelector";
    static components = {
        Popover: ModelFieldSelectorPopover,
    };
    static props = {
        resModel: String,
        path: { optional: true },
        allowEmpty: { type: Boolean, optional: true },
        readonly: { type: Boolean, optional: true },
        readProperty: { type: Boolean, optional: true },
        showSearchInput: { type: Boolean, optional: true },
        isDebugMode: { type: Boolean, optional: true },
        update: { type: Function, optional: true },
        filter: { type: Function, optional: true },
        sort: { type: Function, optional: true },
        followRelations: { type: Boolean, optional: true },
        showDebugInput: { type: Boolean, optional: true },
    };
    static defaultProps = {
        readonly: true,
        allowEmpty: false,
        isDebugMode: false,
        showSearchInput: true,
        update: () => {},
        followRelations: true,
    };

    setup() {
        this.fieldService = useService("field");
        this.popover = usePopover(this.constructor.components.Popover, {
            popoverClass: "o_popover_field_selector",
            onClose: async () => {
                if (this.newPath !== null) {
                    const fieldInfo = await this.fieldService.loadFieldInfo(
                        this.props.resModel,
                        this.newPath
                    );
                    this.props.update(this.newPath, fieldInfo);
                }
            },
        });
        this.keepLast = new KeepLast();
        this.state = useState({ isInvalid: false, displayNames: [] });
        onWillStart(() => this.updateState(this.props));
        onWillUpdateProps((nextProps) => {
            const modelPathKeys = ["resModel", "path", "allowEmpty"];
            if (modelPathKeys.some((key) => this.props[key] !== nextProps[key])) {
                this.updateState(nextProps);
            }
        });
    }

    openPopover(currentTarget) {
        if (this.props.readonly) {
            return;
        }
        this.newPath = null;
        this.popover.open(currentTarget, {
            resModel: this.props.resModel,
            path: this.props.path,
            readProperty: this.props.readProperty,
            update: (path, _fieldInfo, debug = false) => {
                this.newPath = path;
                if (!debug) {
                    this.updateState({ ...this.props, path }, true);
                }
            },
            showSearchInput: this.props.showSearchInput,
            isDebugMode: this.props.isDebugMode,
            filter: this.props.filter,
            sort: this.props.sort,
            followRelations: this.props.followRelations,
            showDebugInput: this.props.showDebugInput,
        });
    }

    async updateState(params, isConcurrent) {
        const { resModel, path, allowEmpty } = params;
        let prom = this.fieldService.loadPathDescription(resModel, path, allowEmpty);
        if (isConcurrent) {
            prom = this.keepLast.add(prom);
        }
        const state = await prom;
        Object.assign(this.state, state);
    }

    clear() {
        if (this.popover.isOpen) {
            this.newPath = "";
            this.popover.close();
            return;
        }
        this.props.update("", { resModel: this.props.resModel, fieldDef: null });
    }
}
Registry / API
Registry name
ModelFieldSelector
Category
Module
web
Slug
model-field-selector
Nav group
data_display