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. }, });
getters与setters同样支持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对象来上传数据,比如腾讯的COS的putObject方法。此时就需要将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";