这是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视图下的所有文件基本上就讲完了。讲的比较粗,碰到具体项目的时候再去扣代码吧。