Odoo Web:常用工具函数

481 阅读4分钟

Odoo Web框架提供了很多的工具函数,利用好这些工具函数可以简化开发。这些工具函数分为两部分,一部分是Odoo Web框架通用的,另一部分提供给开发视图时使用。(本文代码基于Odoo16

Web框架通用函数

代码位置:/web/static/src/core/utils

patch

如果Odoo Web框架提供的扩展方式不能满足扩展需求,可以考虑使用patch函数对要扩展的内容进行monkey patch

/**
 * @param {Object} obj 需要patch的对象,可以是Object、Class、OWL组件
 * @param {string} patchName 补丁名称,unpatch方法使用该名称作为参数,移除名称对应的补丁,名称需要具有唯一性
 * @param {Object} patchValue 补丁的内容
 * @param {{pure?: boolean}} [options] pure如果为false,则补丁操作中不会绑定_super属性
 */
export function patch(obj, patchName, patchValue, options = {}) {...}

对象的patch操作

注意: 在 Odoo17 中 patch 函数的第二个参数已经移除

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

const object = {
  field: "a field",
  fn() {
    // do something
  },
};

patch(object, "patch name", {
  fn() {
    // do things
  },
});

当对函数进行patch操作时,我们可能需要访问父函数。由于我们操作的是对象而不是ES6的类,因此我们不能使用super关键字。Odoo提供了一个特殊的方法this._super去模拟关键字super:

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

⚠️注意

this._super的指向会在补丁函数被调用后重新分配,这就意味着如果在patch中使用了异步函数,就不能在await之后调用this._super,因为this._super的指向不一定是你预期的结果。正确的使用方法就是为初始的_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.
  },
});

getterssetters同样支持patch操作

patch(object, "getter/setter patch", {
  get number() {
    return this._super() / 2;
  },
  set number(value) {
    this._super(value * 2);
  },
});

类的patch操作

对类的实例方法进行patch操作时,需要使用类的prototype属性,如果是对类的静态方法进行patch,则直接使用类

class MyClass {
  static myStaticFn() {...}
  myPrototypeFn() {...}
}

// 对静态方法进行patch
patch(MyClass, "static patch", {
  myStaticFn() {...},
});

// 对实例方法进行patch
patch(MyClass.prototype, "prototype patch", {
  myPrototypeFn() {...},
});

类的构造函数不能被直接patch,我们只能通过对构造函数中调用的方法进行patch来间接patch构造函数:

class MyClass {
  constructor() {
    this.setup();
  }
  setup() {
    this.number = 1;
  }
}

patch(MyClass.prototype, "constructor", {
  setup() {
    this._super(...arguments);
    this.doubleNumber = this.number * 2;
  },
});

OWL组件的patch操作

OWL组件就是一个类,因此对类的patch操作同样适应于OWL组件。组件的构造函数中调用了setup()方法,如果需要对构造函数进行patch操作,可以这样做:

patch(MyComponent.prototype, "my patch", {
  setup() {
    this._super(...arguments);
    // 为组件新增钩子
    this.dialog = useService("dialog");
  },
});

unpatch

unpatch用于移除补丁,是patch的反向操作。

/**
 * @param {Object} obj
 * @param {string} patchName
 */
export function unpatch(obj, patchName) {...}

unpatch通常在测试代码中使用

import { patch, unpatch } from "@web/core/utils/patch";
patch(object, "patch name", { ... });
// test stuff here
unpatch(object, "patch name");

memoize

对函数的第一个参数进行缓存,如果第一参数不变,则不会重复调用。(函数式编程,函数柯里化)

import { memoize } from "@web/core/utils/functions";

应用示例

// url不变不会重复加载JS
export const _loadJS = (assets.loadJS = memoize(function loadJS(url) {
    ...
}));

fuzzyLookup

模糊搜索函数

export function fuzzyLookup(pattern, list, fn) {...}
  • 导入方式:import { fuzzyLookup } from "@web/core/utils/search"
  • 参数说明:
    • pattern:搜索的内容
    • list:内容数组,pattern与数组中的元素进行匹配
    • fn:自定义函数,在对list参数进行遍历时,调用fn函数从数组元素中取值,比如list的元素是一个对象,fn函数可以指定取对象哪个属性
  • 返回值:返回匹配内容的数组,数组的元素从参数list中取得

使用示例

import { fuzzyLookup } from "@web/core/utils/search"

// this.partners [{label: xxx, res_id: xxx}, ...]
const fuzzySearch = fuzzyLookup(pattern, this.partners, (partner) => partner.label);
// 返回内容fuzzySearch [{label: xxx, res_id: xxx}, ...]

url

import { url } from "@web/core/utils/urls";

this.lastURL = url("/web/image", {
    model: this.props.record.resModel,
    id: this.props.record.resId,
    field: previewFieldName,
    unique: imageCacheKey(this.rawCacheKey),
});

objectToUrlEncodedString

该方法可以将一个对象转变为url参数字符串

import { objectToUrlEncodedString } from "@web/core/utils/urls";

const obj = {a: "x", b: 2};
objectToUrlEncodedString(obj);
// 函数返回 "a=x&b=2"

getDataURLFromFile

File对象中获取文件的DataURL

  • DataURL: data:image/jpeg;base64,....,可以直接放到<img>标签的src属性中使用

下面代码摘自OdooFileUploader组件

import { getDataURLFromFile } from "@web/core/utils/urls";

const data = await getDataURLFromFile(file);

有时候需要使用Blob对象来上传数据,比如腾讯的COSputObject方法。此时就需要将DataURL转换为Blob对象

const resp = await fetch(data);
// 使用resp.blob() 即可获取 Blob()对象

视图专用工具函数

  • 代码位置:/web/static/src/views/utils.js
  • 导入方式:import { 函数名称 } from "@web/views/utils";

isX2Many

判断field是否为one2many many2many字段

/**
 * @param {any} field
 * @returns {boolean}
 */
 export function isX2Many(field) {
    return field && X2M_TYPES.includes(field.type);
}

uuid

import { uuid } from "@web/views/utils";