这是view系列的第二篇文章,介绍view_service.js
static\src\views\view_service.js
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { UPDATE_METHODS } from "@web/core/orm_service";
export const viewService = {
dependencies: ["orm"],
start(env, { orm }) {
let cache = {};
function clearCache() {
cache = {};
const processedArchs = registry.category("__processed_archs__");
processedArchs.content = {};
processedArchs.trigger("UPDATE");
}
env.bus.addEventListener("CLEAR-CACHES", clearCache);
env.bus.addEventListener("RPC:RESPONSE", (ev) => {
const { model, method } = ev.detail.data.params;
if (["ir.ui.view", "ir.filters"].includes(model)) {
if (UPDATE_METHODS.includes(method)) {
clearCache();
}
}
});
/**
* Loads various information concerning views: fields_view for each view,
* fields of the corresponding model, and optionally the filters.
*
* @param {LoadViewsParams} params
* @param {LoadViewsOptions} [options={}]
* @returns {Promise}
*/
async function loadViews(params, options = {}) {
const { context, resModel, views } = params;
const loadViewsOptions = {
action_id: options.actionId || false,
load_filters: options.loadIrFilters || false,
toolbar: (!context?.disable_toolbar && options.loadActionMenus) || false,
};
for (const key in options) {
if (!["actionId", "loadIrFilters", "loadActionMenus"].includes(key)) {
loadViewsOptions[key] = options[key];
}
}
if (env.isSmall) {
loadViewsOptions.mobile = true;
}
const filteredContext = Object.fromEntries(
Object.entries(context || {}).filter(
([k, v]) => k == "lang" || k.endsWith("_view_ref")
)
);
const key = JSON.stringify([resModel, views, filteredContext, loadViewsOptions]);
if (!cache[key]) {
debugger
cache[key] = orm
.call(resModel, "get_views", [], {
context: filteredContext,
views,
options: loadViewsOptions,
})
.then((result) => {
const { models, views } = result;
const viewDescriptions = {
fields: models[resModel],
relatedModels: models,
views: {},
};
for (const viewType in views) {
const { arch, toolbar, id, filters, custom_view_id } = views[viewType];
const viewDescription = { arch, id, custom_view_id };
if (toolbar) {
viewDescription.actionMenus = toolbar;
}
if (filters) {
viewDescription.irFilters = filters;
}
viewDescriptions.views[viewType] = viewDescription;
}
return viewDescriptions;
})
.catch((error) => {
delete cache[key];
return Promise.reject(error);
});
}
return cache[key];
}
return { loadViews };
},
};
registry.category("services").add("view", viewService);
1 、服务开头提供了一个清理缓存的函数clearCache, 将注册表中的registry.category(“processed_archs”)清空,并且触发它的update事件。
2、后面就是最重要的loadViews函数了,加载有关视图的各种信息:每个视图的字段视图,对应模型的字段,以及可选的过滤器。
输入参数params: 包含了上下文信息,模型,视图 ,举个例子:
context:{params:{action:201,cids:1,menu_id:124,model:'crax_demo.crax.demo',view_type:'list'}}
resModel:crax_demo.crax.demo
views:[[false,'list'],[false,'form'],[false,'search']]
输入参数option:是一个字典
actionid
loadActionMenus:true
LoadIrFilters:true
这一段代码是对传入的参数进行结构赋值,并且组装传入的option到loadViewsOptions ,同时组装filteredContext
const { context, resModel, views } = params;
const loadViewsOptions = {
action_id: options.actionId || false,
load_filters: options.loadIrFilters || false,
toolbar: (!context?.disable_toolbar && options.loadActionMenus) || false,
};
for (const key in options) {
if (!["actionId", "loadIrFilters", "loadActionMenus"].includes(key)) {
loadViewsOptions[key] = options[key];
}
}
if (env.isSmall) {
loadViewsOptions.mobile = true;
}
const filteredContext = Object.fromEntries(
Object.entries(context || {}).filter(
([k, v]) => k == "lang" || k.endsWith("_view_ref")
)
);
然后将这些变量转换成字符串
const key = JSON.stringify([resModel, views, filteredContext, loadViewsOptions]);
判断缓存中是否有这个key,如果有,则从缓存中返回,否则调用orm从后端获取
cache[key] = orm
.call(resModel, "get_views", [], {
context: filteredContext,
views,
options: loadViewsOptions,
})
.then((result) => {
const { models, views } = result;
const viewDescriptions = {
fields: models[resModel],
relatedModels: models,
views: {},
};
for (const viewType in views) {
const { arch, toolbar, id, filters, custom_view_id } = views[viewType];
const viewDescription = { arch, id, custom_view_id };
if (toolbar) {
viewDescription.actionMenus = toolbar;
}
if (filters) {
viewDescription.irFilters = filters;
}
viewDescriptions.views[viewType] = viewDescription;
}
return viewDescriptions;
})
.catch((error) => {
delete cache[key];
return Promise.reject(error);
});
orm是一个promise,返回值直接付给了缓存, 后面统一返回缓存中的值。
call, then ,catch 这是promise标准的写法。
.call(resModel, "get_views", [], {
context: filteredContext,
views,
options: loadViewsOptions,
})
任何一个模型都有一个get_views方法,这是继承自它的父类 base,具体的实现是在base模块的ir_ui_view中
odoo\addons\base\models\ir_ui_view.py
@api.model
def get_views(self, views, options=None):
""" Returns the fields_views of given views, along with the fields of
the current model, and optionally its filters for the given action.
The return of the method can only depend on the requested view types,
access rights (views or other records), view access rules, options,
context lang and TYPE_view_ref (other context values cannot be used).
Python expressions contained in views or representing domains (on
python fields) will be evaluated by the client with all the context
values as well as the record values it has.
:param views: list of [view_id, view_type]
:param dict options: a dict optional boolean flags, set to enable:
``toolbar``
includes contextual actions when loading fields_views
``load_filters``
returns the model's filters
``action_id``
id of the action to get the filters, otherwise loads the global
filters or the model
:return: dictionary with fields_views, fields and optionally filters
"""
省略100行。。。。
return result
views: views:[[false,‘list’],[false,‘form’],[false,‘search’]]
options: actionid 等
返回值:
views : 各种类型view的id 和arch 以及其他信息
models: 模型的字段信息
.then((result) => {
const { models, views } = result;
const viewDescriptions = {
fields: models[resModel],
relatedModels: models,
views: {},
};
for (const viewType in views) {
const { arch, toolbar, id, filters, custom_view_id } = views[viewType];
const viewDescription = { arch, id, custom_view_id };
if (toolbar) {
viewDescription.actionMenus = toolbar;
}
if (filters) {
viewDescription.irFilters = filters;
}
viewDescriptions.views[viewType] = viewDescription;
}
return viewDescriptions;
这一段是根据orm的返回值 组装viewDescriptions 字段,并返回,这个字段包含了:
fields: models[resModel],
relatedModels: models,
views: {},
这里有个关键点 relatedModels, 通过debug可以看到,这是一个模型的所有的字段信息。是视图的显示中,它起到了驱动的作用。 关于这一点,后面再细聊。
后面的for循环是为了填充views这个字段,对于每种类型的视图,都包含了arch,id,custom_view_id等字段。
总结一波:
view_service有两个功能:
1、 清理缓存
2、从缓存中获取视图信息,如果没有则调用orm服务调用model的get_views方法从后端读取, 然后将返回的信息封装成一个对象返回,这个对象包含了
模型的字段信息
模型信息(包括模型的所有字段以及字段的相关信息),relatedModels 在前端是一个非常重要的概念
视图信息(各种类型视图的id,xml结构等)
关于menuService的介绍就告一段落。