odoo前端视图加载的架构分析
1.视图入口
用F12定位到比如tree视图的地方,可以看到odoo的最顶层定义了一个view组件,然后根据视图类型到了各自的视图结构中。
可以看到,在view视图中,odoo已经通过load方法加载了不少参数,主要是action中的参数。
2.视图结构
对比各种视图的代码目录,结构都是相同的。以tree视图为例,进入list代码目录中,看到代码目录。
这里介绍主要的组成部分,每个视图主要由ArchParser、Controller、Renderer、Model组成。
2.1.ArchParser
ArchParser是用于解析view的xml代码,在list视图中,需要解析的是tree视图,解析结果:
2.2.Controller
Controller中的分为两部分,上半部分主要由操作按钮、搜索框等组成,下半部分通过props.Renderer在Renderer中实现。
2.3.Renderer
Renderer在list视图中实现controller中的下半部分,在controller中通过props.Renderer在Renderer调用。
2.4.Model
Model用于从数据库读取数据,并且会根据视图需要进行相应的处理,odoo对常用数据通过@web/model/relational_model/relational_model中进行处理,如有特殊需求,可以自行构建Model。
model的创建是在Controller的setup中完成,在controller的template中调用。
3.视图实现
最终view中通过调用t-component="Controller"来完成对controller中的调用。
4.新增视图report
4.1.需求描述
在上一章《在报表上方增加搜索视图》中,实现了报表上方增加搜索视图,这种方式由于没有controller,导致searchModel等一系列的组件无法获取,实现麻烦、可扩展性差。因此通过新增视图类型report来实现。
4.2.新增视图类型report
class ActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(selection_add=[('report', "Report")], ondelete={'report': 'cascade'})
class View(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(selection_add=[('report', "Report")])
在report中指定props.Renderer
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_asset_liabilities_report_report" model="ir.ui.view">
<field name="name">Asset Liabilities Report</field>
<field name="model">asset.liabilities.report</field>
<field name="arch" type="xml">
<report string="资产负债表" template_id="jcerp_account_ledger.AssetLiabilitiesReport"/>
</field>
</record>
<record id="action_asset_liabilities_report_test" model="ir.actions.act_window">
<field name="name">资产负债表</field>
<field name="res_model">asset.liabilities.report</field>
<field name="view_mode">report</field>
<field name="view_id" ref="view_asset_liabilities_report_report"/>
</record>
</data>
</odoo>
4.3.ArchParser
在ArchParser需要获取report视图传过来的template名称。
export class ReportArchParser {
parse(arch, fields = {}) {
const archInfo = { fields, fieldAttrs: {}};
visitXML(arch, (node) => {
switch (node.tagName) {
case "report": {
if (node.hasAttribute("template_id")) {
archInfo.template_id = node.getAttribute("template_id");
}
const title = node.getAttribute("string");
if (title) {
archInfo.title = title;
}
break;
}
}
});
return archInfo;
}
}
4.4.Renderer
由于Renderer是根据template_id生成,因此这里不需要实现Renderer
4.5.Model
根据search传过来的参数获取并解析字段
/**
* @param {SearchParams} searchParams
*/
async load(searchParams) {
this.searchParams = searchParams;
if (!this.initialGroupBy) {
this.initialGroupBy = searchParams.context.graph_groupbys || this.metaData.groupBy; // = arch groupBy --> change that
}
this.data = {'value': '测试'}
// const metaData = this._buildMetaData();
// await this._fetchDataPoints(metaData);
}
4.6.Controller
在useEffect中用传过来的template_xmlid,model中的解析数据生成template页面,并且替换o_content。
export class ReportController extends Component {
setup() {
this.archInfo = this.props.archInfo;
this.model = useModelWithSampleData(this.props.Model, this.props.modelParams);
useSetupView({
rootRef: useRef("root"),
getLocalState: () => {
return { metaData: this.model.metaData };
}
});
this.rootRef = useRef("root");
this.searchBarToggler = useSearchBarToggler();
useEffect(() => this.renderReport());
}
renderReport() {
const innerHTML = renderToString(this.archInfo.template_xmlid, {
model: this.model
});
const template = Object.assign(document.createElement("template"), { innerHTML });
const tooltip = template.content.firstChild;
// 将o_content替换为tooltip
this.rootRef.el.querySelector('.o_content').replaceWith(tooltip);
}
}
4.6.实现效果
5.总结
通过新增视图类型的方式
1、实现了controller的上半部分统一的效果,比如搜索的多选,新增数字筛选等
2、减少了report开发人员的工作量,开发人员可以专注于template的实现,真正做到组件化。