第五章 抽象视图

我们在前一章学到了,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进行传递)。

results matching ""

    No results matching ""