odoo17核心概念view3——view其他重要的概念

这是view系列的第三篇文章,介绍一下view里其他一些重要的概念。

1、模板xml文件最终怎么编译成html文件的

原理大概如下图所示

在这里插入图片描述
分了三个步骤:

1、xml编译成owl模板

这一步是通过ViewCompiler类完成的,这个类是一个基类,在static\src\views\view_compiler.js 文件中。

export class ViewCompiler {
    constructor(templates) {
        /** @type {number} */
        this.id = 1;
        /** @type {Compiler[]} */
        this.compilers = [
            {
                selector: "a[type]:not([data-bs-toggle]),a[data-type]:not([data-bs-toggle])",
                fn: this.compileButton,
            },
            {
                selector: "button:not([data-bs-toggle])",
                fn: this.compileButton,
                doNotCopyAttributes: true,
            },
            { selector: "field", fn: this.compileField },
            { selector: "widget", fn: this.compileWidget },
        ];
        this.templates = templates;
        this.ctx = { readonly: "__comp__.props.readonly" };

        this.owlDirectiveRegexesWhitelist = this.constructor.OWL_DIRECTIVE_WHITELIST.map(
            (d) => new RegExp(d)
        );
        this.setup();
    }

在构造函数中,定义了一系列的编译器,一个selector对应一个编译函数。我们看看button编译成了什么?

  const button = createElement("ViewButton", {
            tag: toStringExpression(tag),
            record: recordExpr,
        });

我们可以看到button编译成了ViewButton,这是一个owl组件。
field 的属性 invisible编译成什么了呢?在applyInvisible函数中,invisible 编译成了t-if
上面说了,这是一个基类,每种视图都有扩展类,我们看看form视图的扩展类。


export class FormCompiler extends ViewCompiler {
    setup() {
        this.encounteredFields = {};
        /** @type {Record<string, Element[]>} */
        this.labels = {};
        this.noteBookId = 0;
        this.compilers.push(
            ...compilersRegistry.getAll(),
            { selector: "div[name='button_box']", fn: this.compileButtonBox },
            { selector: "form", fn: this.compileForm, doNotCopyAttributes: true },
            { selector: "group", fn: this.compileGroup },
            { selector: "header", fn: this.compileHeader },
            { selector: "label", fn: this.compileLabel, doNotCopyAttributes: true },
            { selector: "notebook", fn: this.compileNotebook },
            { selector: "setting", fn: this.compileSetting },
            { selector: "separator", fn: this.compileSeparator },
            { selector: "sheet", fn: this.compileSheet }
        );
    }

FormCompiler 继承自ViewCompiler 对form视图中特有的一些标签分别编写了编译函数。

看板视图的Comiler

export class KanbanCompiler extends ViewCompiler {
    setup() {
        this.ctx.readonly = "read_only_mode";
        this.compilers.push(
            { selector: ".oe_kanban_colorpicker", fn: this.compileColorPicker },
            { selector: "t[t-call]", fn: this.compileTCall },
            { selector: "img", fn: this.compileImage }
        );
    }

不一 一举例了。

2、form_arch_parser.js

static\src\views\form\form_arch_parser.js
代码不长,这是xml文件的解析器,也就是将xml文件中的节点属性解析出来,组装成一个结构体返回。

/** @odoo-module **/

import { visitXML } from "@web/core/utils/xml";
import { Field } from "@web/views/fields/field";
import { Widget } from "@web/views/widgets/widget";
import { archParseBoolean, getActiveActions } from "@web/views/utils";

export class FormArchParser {
    parse(xmlDoc, models, modelName) {
        const jsClass = xmlDoc.getAttribute("js_class");
        const disableAutofocus = archParseBoolean(xmlDoc.getAttribute("disable_autofocus") || "");
        const activeActions = getActiveActions(xmlDoc);
        const fieldNodes = {};
        const widgetNodes = {};
        let widgetNextId = 0;
        const fieldNextIds = {};
        let autofocusFieldId = null;
        visitXML(xmlDoc, (node) => {
            if (node.tagName === "field") {
                const fieldInfo = Field.parseFieldNode(node, models, modelName, "form", jsClass);
                if (!(fieldInfo.name in fieldNextIds)) {
                    fieldNextIds[fieldInfo.name] = 0;
                }
                const fieldId = `${fieldInfo.name}_${fieldNextIds[fieldInfo.name]++}`;
                fieldNodes[fieldId] = fieldInfo;
                node.setAttribute("field_id", fieldId);
                if (archParseBoolean(node.getAttribute("default_focus") || "")) {
                    autofocusFieldId = fieldId;
                }
                if (fieldInfo.type === "properties") {
                    activeActions.addPropertyFieldValue = true;
                }
                return false;
            } else if (node.tagName === "div" && node.classList.contains("oe_chatter")) {
                // remove this when chatter fields are declared as attributes on the root node
                return false;
            } else if (node.tagName === "widget") {
                const widgetInfo = Widget.parseWidgetNode(node);
                const widgetId = `widget_${++widgetNextId}`;
                widgetNodes[widgetId] = widgetInfo;
                node.setAttribute("widget_id", widgetId);
            }
        });
        return {
            activeActions,
            autofocusFieldId,
            disableAutofocus,
            fieldNodes,
            widgetNodes,
            xmlDoc,
        };
    }
}

  activeActions,        列表制图选中行后动作按钮中的动作
  autofocusFieldId,		自动获得焦点的字段,设置了default_focus属性
  disableAutofocus,		禁止获得焦点
  fieldNodes,			字段节点
  widgetNodes,			widget节点
  xmlDoc,				xmlDoc

3、Controller和Renderer

Controller:

主要指标题栏下方,内容区上方控制的部分,包括了新建按钮,面包屑导航、搜索框,分页等
下面是FormController 模板、子组件以及props的定义。

FormController.template = `web.FormView`;
FormController.components = {
    FormStatusIndicator,
    Layout,
    ButtonBox,
    ViewButton,
    Field,
    CogMenu,
};
FormController.props = {
    ...standardViewProps,
    discardRecord: { type: Function, optional: true },
    mode: {
        optional: true,
        validate: (m) => ["edit", "readonly"].includes(m),
    },
    saveRecord: { type: Function, optional: true },
    removeRecord: { type: Function, optional: true },
    Model: Function,
    Renderer: Function,
    Compiler: Function,
    archInfo: Object,
    buttonTemplate: String,
    preventCreate: { type: Boolean, optional: true },
    preventEdit: { type: Boolean, optional: true },
    onDiscard: { type: Function, optional: true },
    onSave: { type: Function, optional: true },
};
FormController.defaultProps = {
    preventCreate: false,
    preventEdit: false,
};

Renderer:

内容的主体区域
从下方代码中可以看出,renderer的子组件有很多 ,常见的Field,ViewButton,Widget,Notebook 等,好玩的是
默认属性中有一个空对象,一个空函数,仿佛是留给用户扩展的


FormRenderer.template = xml`<t t-call="{{ templates.FormRenderer }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }" />`;
FormRenderer.components = {
    Field,
    FormLabel,
    ButtonBox,
    ViewButton,
    Widget,
    Notebook,
    Setting,
    OuterGroup,
    InnerGroup,
    StatusBarButtons,
};
FormRenderer.props = {
    archInfo: Object,
    Compiler: { type: Function, optional: true },
    record: Object,
    // Template props : added by the FormCompiler
    class: { type: String, optional: 1 },
    translateAlert: { type: [Object, { value: null }], optional: true },
    onNotebookPageChange: { type: Function, optional: true },
    activeNotebookPages: { type: Object, optional: true },
    setFieldAsDirty: { type: Function, optional: true },
};
FormRenderer.defaultProps = {
    activeNotebookPages: {},
    onNotebookPageChange: () => {},
};

4、其他的一些文件

static\src\views\form\form_label.js FormLabel组件
static\src\views\form\form.variables.scss css3的变量
form目录下的一些子目录都是一些组件,列一下:

button_box
form_error_dialog
form_goup
form_status_indicator
setting
status_bar_buttons

有机会用到的时候再好好看看。
这样form视图下的所有文件基本上就讲完了。讲的比较粗,碰到具体项目的时候再去扣代码吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值