第五章 抽象视图
我们在前一章学到了,WC中的视图由工厂类(Factory)将数据、控制器和渲染器组织在一起而成。而我们知道odoo中又有各种各样的视图类型,如表单、列表、看板、透视图等等。很显然,这里需要一个抽象的类将它们的共同属性抽象出来一个父类,而各种类型的视图将通过继承的方式各自实现自己的特定属性。本章的主角就是这个共同的父类:抽象视图。
视图我们将使用关键字View替代Factory,这样很显然更容易被理解。视图在WC中的作用是读取用户在XML视图中的字段和布局,创建控制器(controller)和渲染器(renderer),并完成渲染,最终展示给终端用户。
抽象视图
我们先来看一下抽象视图的定义
var AbstractView = Factory.extend({
// name displayed in view switchers
display_name: '',
// indicates whether or not the view is mobile-friendly
mobile_friendly: false,
// icon is the font-awesome icon to display in the view switcher
icon: 'fa-question',
// multi_record is used to distinguish views displaying a single record
// (e.g. FormView) from those that display several records (e.g. ListView)
multi_record: true,
// viewType is the type of the view, like 'form', 'kanban', 'list'...
viewType: undefined,
// determines if a search bar is available
withSearchBar: true,
// determines the search menus available and their orders
searchMenuTypes: ['filter', 'groupBy', 'favorite'],
// determines if a control panel should be instantiated
withControlPanel: true,
// determines if a search panel could be instantiated
withSearchPanel: true,
// determines the MVC components to use
config: _.extend({}, Factory.prototype.config, {
Model: AbstractModel,
Renderer: AbstractRenderer,
Controller: AbstractController,
SearchPanel: SearchPanel,
})
...
}
抽象视图拥有自己对应的抽象模型、抽象控制器和抽象渲染器。这三个我们会在后面进行介绍。
抽象视图的几个重要属性:
- viewType: 标识视图的类型,如form、kanban或list等。对于抽象视图来说,它的类型是undefined
- withControlPanel: 是否带有控制栏,默认为true
- withSearchPanel: 是否带有搜索栏,默认为true
初始化
抽象视图初始化的方法,接收两个参数:
- viewInfo: 视图信息(object)
- params: 初始化需要的参数(object)
视图信息对象的主要结构如下:
- viewInfo.arch: 视图布局文件
- viewInfo.fields: 字段信息
- viewInfo.fieldsInfo: 字段结构信息
fields和fieldsInfo的区别,我们看下面的一个例子就一目了然了:
由此,我们可以看出来,fields是字段的内置属性,是描述字段本身属性的集合。而fieldsInfo是跟视图相关的配置属性,是视图如何处理这个字段的属性集合。
viewInfo的类型可以是string也可以是一个对象。如果是string,抽象视图会使用_processFieldsView方法来将字符串转化为对象,然后传递给fieldsView属性。否则,将直接赋值给fieldsView。
[TODO..]
获取控制器
getController: async function () {
const _super = this._super.bind(this);
const { searchModel } = this.controllerParams;
await searchModel.load();
this._updateMVCParams(searchModel.get("query"));
// get the parent of the model if it already exists, as _super will
// set the new controller as parent, which we don't want
const modelParent = this.model && this.model.getParent();
const [controller] = await Promise.all([
_super(...arguments),
searchModel.isReady(),
]);
if (modelParent) {
// if we already add a model, restore its parent
this.model.setParent(modelParent);
}
return controller;
},
唯一需要留意一下的就是在获取控制器的内部方法中, 实际上是先获取了模型的父类, 然后在调用了父类方法获取控制器之后, 又重新将原先的模型父类设置到模型上, 原因就是父类方法会将控制器的父类设置为新的Controller, 这破坏了我们数据的上下级关系.
获取模型
getModel: function () {
if (!this.model) {
this.model = this._super.apply(this, arguments);
}
return this.model;
},
这个方法就没什么好说的了,就是获取当前视图绑定的数据模型.
总结
抽象视图中并没有重载getRenderer方法, 也就是直接使用了工厂类的渲染方法.各类型的视图会自己重载并实现自己的渲染方法. 抽象视图在工厂类的基础上并没有增添过多的功能, 只是把接口式的方法细化了, 形成各视图的一套标准.
抽象模型(AbstractModel)是MVC中的M部分,提到MVC的M我们一般都倾向于服务端的概念,但是这里我们的说的是Web端的概念。Model的职责是获取相关数据供视图的其他组件使用,当然视图中数据的变化也应该及时传递给Model。Model并非一个Widget,它不需要用来渲染和展示。但是,它是EventDispatcherMixin的子类,也就是说,它可以通过事件冒泡的方式与父类通信。
var AbstractModel = mvc.Model.extend({
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* When something changes, the data may need to be refetched. This is the
* job for this method: reloading (only if necessary) all the data and
* making sure that they are ready to be redisplayed.
*
* @param {Object} params
* @returns {Promise}
*/
reload: function (params) {
return Promise.resolve();
},
/**
* Processes date(time) and selection field values sent by the server.
* Converts data(time) values to moment instances.
* Converts false values of selection fields to 0 if 0 is a valid key,
* because the server doesn't make a distinction between false and 0, and
* always sends false when value is 0.
*
* @param {Object} field the field description
* @param {*} value
* @returns {*} the processed value
*/
_parseServerValue: function (field, value) {
if (field.type === 'date' || field.type === 'datetime') {
// process date(time): convert into a moment instance
value = fieldUtils.parse[field.type](value, field, {isUTC: true});
} else if (field.type === 'selection' && value === false) {
// process selection: convert false to 0, if 0 is a valid key
var hasKey0 = _.find(field.selection, function (option) {
return option[0] === 0;
});
value = hasKey0 ? 0 : value;
}
return value;
},
});
抽象模型的代码很少,只是在mvc的Model模型基础上新增了两个方法:
- reload: reload方法的目的是为了在必要时重新获取数据,这里只是方法定义,具体的实现需要子类自行实现。
- _parseServerValue: _parseServerValue的作用是处理服务端传回的时间日期类型和选择类型的数据。对于时间日期类型,_parseServerValue方法会将其转换为瞬时对象。对于选择类型,将会将false转换为0(因为服务器端总会将0转换为false进行传递)。