第四章 组件(Component)

OWL中的组件

我们先来的看OWL(web/static/lib/owl)中组件的定义:

class Component {
    constructor(props, env, node) {
        this.props = props;
        this.env = env;
        this.__owl__ = node;
    }
    setup() { }
    render(deep = false) {
        this.__owl__.render(deep === true);
    }
}

Component.template = "";

可以看出Component本身没有什么太复杂的代码,只有一个构造函数和两个常规函数。

  • setup: 初始化时的设置方法
  • render: 渲染方法

Odoo中所有的组件均是继承自OWL的Component。

自定义组件

如果我们想要自定义一个组件,那么可以按照下面方式进行:

class MyComponent extends Component {
    static template = "template_name";
    setup() {
        ...
    }
}

registry.category("view_widgets").add("my_component",{component: MyComponent})

WebClient

我们在经典的WebClient世界中已经对它非常熟悉了,但是从odoo开始使用OWL开始重构WebClient时,我们就注定了要以一种新的思维方式来认识它的新面孔。

我们先来看一下WebClient的架构图:

webclient

加载

WebClient是加载到DOM的body标签的主要组件,它通过startWebClient方法实现加载的:

const root = await mount(Webclient, { env, target: document.body, position: "self" });

组成

WebClient.components = {
    ActionContainer,
    NavBar,
    NotUpdatable,
    MainComponentsContainer,
};
WebClient.template = "web.WebClient";

由WebClient的组件定义可以看出,WebClient主要由以下四个组件组成:

  • NavBar: Odoo顶端的导航栏
  • ActionContainer: 动作容器
  • MainComponentsContainer: 组件所在的容器
  • NotUpdatable: 重载了shouldUpdate方法并返回false值的基本包装组件,用于创建静态组件

初始化

setup() {
    this.menuService = useService("menu");
    this.actionService = useService("action");
    this.title = useService("title");
    this.router = useService("router");
    this.user = useService("user");
    ...
    if (this.env.debug) {
        registry.category("systray").add(
            "web.debug_mode_menu",
            {
                Component: DebugMenu,
            },
            { sequence: 100 }
        );
    }
    this.localization = localization;
    this.title.setParts({ zopenerp: "Odoo" });

WebClient在初始化的过程中,内置了一下几种服务以供后续使用:

  • menuService: 菜单服务
  • actionService: 动作服务
  • title: 标题服务
  • router: 路由服务
  • user: 用户服务

如果用户开启了开发这模式,那么WebClient将显示debug图标,并加载DebugMenu组件。

WebClient使用title服务设置了系统服务名称为"Odoo",并使用了关键字zopenerp。后面我们会介绍如果使用重载机制将系统标题更换为我们自定义的标题。

WebClient初始化的过程中还加载了若干总线,来完成事件响应。

挂载

WebClient挂载到DOM中之后就会触发WEB_CLIENT_READY事件,以通知其他组件开始工作。

Patch组件

在经典的WC实现中如果我们想要通过重载某个模块的的某个方法,可以使用include方法来实现, 到了OWL的世界中,也存在同样的方法, 即Patch方法. 下面我们就来看一下, 如何使用patch方法对一个组件进行重载.

首先patch方法位于web模块的utils包中,因此我们在我们的代码里中需要进行导入:

import { patch } from "@web/core/utils/patch";

patch方法接受四个参数:

  • obj {object}: 需要被patch的对象
  • patchName {string}: patch的名称
  • patchValue {object}: patch的值
  • pure {boolean}: 可选options的参数

这里我们以Mommy Base模块中, 设置系统标题的功能给大家展示如何patchWebClient对象. Mommy Base模块是笔者自己开发的一个模块, 主要用来对系统进行一些初始的个性化设置和增强功能. 这里我们要介绍的是它的设置系统标题功能, 我们都知道Odoo在安装完成后,默认显示的标题是odoo.

现在,我们想要把这个标题改掉,改成可以用户自定义的模式.通过对源码的分析,我们了解到,要想实现对系统标题的修改,就需要使用title服务的setParts方法, 想要获取到用户设置的标题,就需要使用rpc服务, 最后,我们在WebClient的初始化方法中对原有的标题进行修改.

patch(WebClient.prototype, "_webClient", {
    async setup() {
        this._super(...arguments);
        let data = await this.env.services.rpc("/web/dataset/call_kw/ir.config_parameter/get_param", {
            model: "ir.config_parameter",
            method: "get_param",
            args: ["mommy.title"],
            kwargs: {}
        })
        this.title.setParts({ zopenerp: data })
    }
});

odoo的系统标题使用了一个比较奇怪的关键字zopenerp, 原因也比较简单,就是为了方便查找..

super方法的使用

我们在前面的例子中可以看到,通常我们在patch一个方法时,通常想要访问他的父类方法,这里因为我们使用的是patch的对象而非原生ES6对象,因此我们不能使用super关键字,odoo为此专门指定了一个关键字_super来使用。

patch(object, "_super patch", {
  fn() {
    this._super(...arguments);
    // do other things
  },
});

通常情况下是可以像上述形式使用。但是也有例外的情况,就是如果我们patch的方法如果是一个异步方法,则不可以这么使用。原因是,this._super在patch之后被重新赋值了,因此在异步方法内部使用this._super它所对应的有可能不是你预想的那个方法。

解决方案是在异步调用前复制一个方法的调用。

patch(object, "async _super patch", {
  async myAsyncFn() {
    const _super = this._super.bind(this);
    await Promise.resolve();
    await _super(...arguments);
    // await this._super(...arguments); // this._super is undefined.
  },
});

父类的父类的调用

前面的例子中,我们对父类的方法调用可以这么处理,如果是对父类的父类,则需要使用调用类的prototype进行替换。


import BarcodeModel from '@stock_barcode/models/barcode_model';
import BarcodeQuantModel from '@stock_barcode/models/barcode_quant_model';
import { patch } from '@web/core/utils/patch';

patch(BarcodeQuantModel.prototype, "mengfu_stock.BarcodeModel", {
    async _createNewLine(params) {
        const _super = BarcodeModel.prototype._createNewLine.bind(this);
        await  Promise.resolve();
        ...

这个例子中 BarcodeQuantModel是BarcodeModel的子类,我们在重载BarcodeQuantModel的_createNewLine方法时,需要直接调用爷类BarcodeModel的_createNewLine方法而不是调用父类。

参考资料

results matching ""

    No results matching ""