框架概述

介绍

Odoo Javascript 框架是由“web/` addon to help build odoo applications running in the browser. At the same time, the Odoo Javascript framework is a single page application, usually known as the web client (available at the url ``/web`”提供的一组功能/构建块。

Web 客户端最初是一个使用自定义类和小部件系统制作的应用程序,但现在正在过渡到使用本机 javascript 类,并将 Owl 作为组件系统。这解释了为什么这两个系统目前都在代码库中使用。

从高层次的角度来看,Web 客户端是一个单页应用程序:它不需要在用户每次执行操作时向服务器请求完整的页面。相反,它只请求它需要的内容,然后相应地替换/更新当前屏幕。此外,它还管理 URL 以使其与当前状态保持同步。

javascript 框架(全部或部分)也用于其他情况,例如 Odoo 网站或销售点。本参考资料主要关注 Web 客户端。

注解

在 Odoo 生态系统中,前端后端 一词通常分别视为 odoo 网站(公共)和 Web 客户端的同义词。不要将此术语与更常见的浏览器代码(前端)和服务器(后端)混淆。

注解

在本文档中,“组件”一词始终指新的 Owl 组件,而“小部件”则指旧的 Odoo 小部件。

注解

如果可能的话,所有新的开发都应该在 Owl 中完成!

代码结构

web/static/src folder contains all the web/ JavaScript(以及 CSS 和模板)代码库。以下是最重要的文件夹的列表:

  • core/ 大部分低级功能

  • fields/ 所有字段组件

  • views/ all javascript views components (form, list,…)

  • search/ 控制面板,搜索栏,搜索面板,…

  • webclient/ Web 客户端特定代码:导航栏、用户菜单、操作服务,…

web/static/src is the root folder. Everything inside can simply be imported by using the @web prefix. For example, here is how one can import the memoize function located in web/static/src/core/utils/functions

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

网络客户端架构

如上所述,Web 客户端是一个 owl 应用程序。这是其模板的稍微简化的版本:

<t t-name="web.WebClient">
    <body class="o_web_client">
        <NavBar/>
        <ActionContainer/>
        <MainComponentsContainer/>
    </body>
</t>

正如我们所看到的,它基本上是导航栏、当前操作和一些附加组件的包装器。 ActionContainer is a higher order component that will display the current action controller (so, a client action, or a specific view in the case of actions of type act_window)。管理操作是其工作的重要组成部分:操作服务在内存中保存所有活动操作的堆栈(在面包屑中表示),并协调每个更改。

另一个值得注意的有趣的事情是“MainComponentsContainer`: it is simply a component that displays all components registered in the ``main_components`”注册表。这就是系统其他部分扩展 Web 客户端的方式。

环境

作为 Owl 应用程序,Odoo Web 客户端定义了自己的环境(组件可以使用 this.env). Here is a description of what Odoo adds to the shared env 对象访问它:

钥匙

价值

qweb

Owl 需要(包含所有模板)

bus

main bus,用于协调一些通用事件

services

所有已部署的 services (通常应使用 useService 挂钩访问)

debug

细绳。如果非空,则 Web 客户端位于 debug mode

_t

翻译功能

isSmall

布尔值。如果为 true,则 Web 客户端当前处于移动模式(屏幕宽度 <= 767px)

因此,例如,要翻译组件中的字符串(注意:模板会自动翻译,因此在这种情况下不需要特定操作),可以这样做:

const someString = this.env._t('some text');

注解

对环境的引用非常强大,因为它提供了对所有服务的访问。这在许多情况下很有用:例如,用户菜单项大多定义为字符串,以及采用 env 作为唯一参数的函数。这足以表达所有用户菜单需求。

积木

大多数 Web 客户端都是使用几种类型的抽象构建的:注册表、服务、组件和挂钩。

登记处

Registries 基本上是一个简单的键/值映射,用于存储某些特定类型的对象。它们是 UI 可扩展性的重要组成部分:一旦注册了某个对象,Web 客户端的其余部分就可以使用它。例如,字段注册表包含可在视图中使用的所有字段组件(或小部件)。

import { Component } from "@odoo/owl";
import { registry } from "./core/registry";

class MyFieldChar extends Component {
    // some code
}

registry.category("fields").add("my_field_char", MyFieldChar);

请注意,我们从“@web/core/registry` then open the sub registry ``fields`”导入主注册表。

服务

Services 是提供功能的长期存在的代码片段。它们可以由组件导入(使用 useService) or by other services. Also, they can declare a set of dependencies. In that sense, services are basically a DI (dependency injection) system. For example, the notification service provides a way to display a notification, or the rpc 服务是向 Odoo 服务器执行请求的正确方法。

以下示例注册一个简单的服务,该服务每 5 秒显示一次通知:

import { registry } from "./core/registry";

const serviceRegistry = registry.category("services");

const myService = {
    dependencies: ["notification"],
    start(env, { notification }) {
        let counter = 1;
        setInterval(() => {
            notification.add(`Tick Tock ${counter++}`);
        }, 5000);
    }
};

serviceRegistry.add("myService", myService);

组件和挂钩

Componentshooks 是来自 Owl component system 的想法。 Odoo 组件只是 owl 组件,是 Web 客户端的一部分。

Hooks 是一种分解代码的方法,即使它取决于生命周期。这是在组件中注入功能的可组合/函数式方式。它们可以被视为一种 mixin。

function useCurrentTime() {
    const state = useState({ now: new Date() });
    const update = () => state.now = new Date();
    let timer;
    onWillStart(() => timer = setInterval(update, 1000));
    onWillUnmount(() => clearInterval(timer));
    return state;
}

语境

Odoo javascript 中的一个重要概念是*上下文*:它为代码提供了一种为函数调用或 rpc 提供更多上下文的方法,以便系统的其他部分可以对该信息做出正确的反应。在某种程度上,它就像一个到处传播的信息包。它在某些情况下很有用,例如让 Odoo 服务器知道模型 rpc 来自特定的表单视图,或者激活/禁用组件中的某些功能。

Odoo Web 客户端中有两种不同的上下文:*用户上下文*和*操作上下文*(因此,我们在使用*上下文*这个词时应该小心:根据情况,它可能意味着不同的事情)。

注解

context 对象在许多情况下可能很有用,但应小心不要过度使用它!许多问题可以用标准方式解决,而无需修改上下文。

用户上下文

*用户上下文*是一个小对象,包含与当前用户相关的各种信息。它可以通过 user 服务获得:

class MyComponent extends Component {
    setup() {
        const user = useService("user");
        console.log(user.context);
    }
}

它包含以下信息:

姓名

类型

描述

allowed_company_ids

number[]

用户的活跃公司 ID 列表

lang

string

用户语言代码(例如“en_us”)

tz

string

用户当前时区(例如“欧洲/布鲁塞尔”)

实际上,orm 服务会自动将用户上下文添加到其每个请求中。这就是为什么在大多数情况下通常不需要直接导入它。

注解

allowed_company_ids 的第一个元素是用户的主要公司。

行动背景

ir.actions.act_windowir.actions.client 支持可选的 context 字段。该字段是代表对象的 char 。每当在 Web 客户端中加载相应的操作时,此上下文字段将被评估为一个对象,并提供给与该操作相对应的组件。

<field name="context">{'search_default_customer': 1}</field>

它可以以多种不同的方式使用。例如,视图将操作上下文添加到向服务器发出的每个请求。另一个重要用途是默认激活某些搜索过滤器(参见上面的示例)。

有时,当我们手动执行新操作时(因此,在 JavaScript 中以编程方式),能够扩展操作上下文会很有用。这可以通过 additional_context 参数来完成。

// in setup
let actionService = useService("action");

// in some event handler
actionService.doAction("addon_name.something", {
    additional_context:{
        default_period_id: defaultPeriodId
    }
});

在此示例中,将加载 xml_id addon_name.something 的操作,并且其上下文将使用 default_period_id 值进行扩展。这是一个非常重要的用例,它允许开发人员通过为下一个操作提供一些信息来将操作组合在一起。

Python解释器

Odoo 框架具有内置的小型 python 解释器。它的目的是评估小的 python 表达式。这很重要,因为 Odoo 中的视图具有用 python 编写的修饰符,但它们需要由浏览器评估。

例子:

import { evaluateExpr } from "@web/core/py_js/py";

evaluateExpr("1 + 2*{'a': 1}.get('b', 54) + v", { v: 33 }); // returns 142

py JavaScript 代码导出 5 个函数:

tokenize(expr)
参数
  • expr (string()) – 要标记化的表达式

返回

Token[] 令牌列表

parse(tokens)
参数
  • tokens (Token[]()) – 令牌列表

返回

AST 表示表达式的抽象语法树结构

parseExpr(expr)
参数
  • expr (string()) – 表示有效 python 表达式的字符串

返回

AST 表示表达式的抽象语法树结构

evaluate(ast[, context])
参数
  • ast (AST()) – 表示表达式的 AST 结构

  • context (Object()) – 提供额外评估上下文的对象

返回

相对于上下文的表达式的任何结果值

evaluateExpr(expr[, context])
参数
  • expr (string()) – 表示有效 python 表达式的字符串

  • context (Object()) – 提供额外评估上下文的对象

返回

相对于上下文的表达式的任何结果值

域名

从广义上讲,Odoo 中的域表示匹配某些指定条件的一组记录。在 JavaScript 中,它们通常表示为条件列表(或运算符列表:前缀表示法中的 |&!),或字符串表达式。它们不必标准化(如果需要,则隐含 & 运算符)。例如:

// list of conditions
[]
[["a", "=", 3]]
[["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]
["&", "&", ["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]
["&", "!", ["a", "=", 1], "|", ["a", "=", 2], ["a", "=", 3]]

// string expressions
"[('some_file', '>', a)]"
"[('date','>=', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"
"[('date', '!=', False)]"

字符串表达式比列表表达式更强大:它们可以包含 python 表达式和未计算的值,这取决于某些计算上下文。然而,操作字符串表达式更加困难。

由于域在 Web 客户端中非常重要,因此 Odoo 提供了 Domain 类:

new Domain([["a", "=", 3]]).contains({ a: 3 }) // true

const domain = new Domain(["&", "&", ["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]);
domain.contains({ a: 1, b: 2, c: 3 }); // true
domain.contains({ a: -1, b: 2, c: 3 }); // false

// next expression returns ["|", ("a", "=", 1), ("b", "<=", 3)]
Domain.or([[["a", "=", 1]], "[('b', '<=', 3)]"]).toString();

这是 Domain 类描述:

class Domain([descr])
参数
  • descr (string | any[] | Domain()) – 域描述

Domain.contains(record)
参数
  • record (Object()) – 一个记录对象

返回

布尔值

如果记录符合域指定的所有条件,则返回 true

Domain.toString()
返回

细绳

返回域的字符串描述

Domain.toList([context])
参数
  • context (Object()) – 评估背景

返回

任何[]

返回域的列表描述。请注意,此方法采用一个可选的 context 对象,该对象将用于替换所有自由变量。

new Domain(`[('a', '>', b)]`).toList({ b:3 }); // [['a', '>', 3]]

Domain 类还提供了 4 个有用的静态方法来组合域:

// ["&", ("a", "=", 1), ("uid", "<=", uid)]
Domain.and([[["a", "=", 1]], "[('uid', '<=', uid)]"]).toString();

// ["|", ("a", "=", 1), ("uid", "<=", uid)]
Domain.or([[["a", "=", 1]], "[('uid', '<=', uid)]"]).toString();

// ["!", ("a", "=", 1)]
Domain.not([["a", "=", 1]]).toString();

// ["&", ("a", "=", 1), ("uid", "<=", uid)]
Domain.combine([[["a", "=", 1]], "[('uid', '<=', uid)]"], "AND").toString();
static Domain.and(domains)
参数

domains (string[] | any[][] | Domain[]) – 域表示列表

返回

领域

返回表示所有域的交集的域。

static Domain.or(domains)
参数

domains (string[] | any[][] | Domain[]) – 域表示列表

返回

领域

返回表示所有域的并集的域。

static Domain.not(domain)
参数

domain (string | any[] | Domain) – 域表示

返回

领域

返回一个表示域参数的否定的域

static Domain.combine(domains, operator)
参数
  • domains (string[] | any[][] | Domain[]) – 域表示列表

  • operator ('AND' or 'OR') – 操作员

返回

领域

返回表示所有域的交集或并集的域,具体取决于运算符参数的值。

公共汽车

Web 客户端 environment 对象包含一个名为 bus 的事件总线。其目的是让系统的各个部分能够正确地自我协调,而不会使它们耦合。 env.bus 是猫头鹰 EventBus,应用于感兴趣的全球事件。

// for example, in some service code:
env.bus.on("WEB_CLIENT_READY", null, doSomething);

以下是可以在此总线上触发的事件列表:

信息

有效载荷

扳机

ACTION_MANAGER:UI-UPDATED

指示 ui 的哪一部分已更新的模式(“当前”、“新”或“全屏”)

向动作管理器请求的动作的呈现已完成

ACTION_MANAGER:UPDATE

下一个渲染信息

动作管理器已完成下一个界面的计算

MENUS:APP-CHANGED

没有任何

菜单服务的当前应用程序已更改

ROUTE_CHANGE

没有任何

url 哈希已更改

RPC:REQUEST

远程过程调用 ID

rpc 请求刚刚开始

RPC:RESPONSE

远程过程调用 ID

一个rpc请求完成

WEB_CLIENT_READY

没有任何

Web客户端已安装

FOCUS-VIEW

没有任何

主视图应该聚焦自己

CLEAR-CACHES

没有任何

应清除所有内部缓存

CLEAR-UNCOMMITTED-CHANGES

函数列表

所有具有未提交更改的视图都应清除它们,并在列表中推送回调

浏览器对象

javascript框架还提供了一个特殊的对象``browser`` that provides access to many browser APIs, like location, localStorage or setTimeout. For example, here is how one could use the ``browser.setTimeout``函数:

import { browser } from "@web/core/browser/browser";

// somewhere in code
browser.setTimeout(someFunction, 1000);

对于测试目的来说,它最有趣:通过在测试期间模拟相关函数,可以轻松测试所有使用浏览器对象的代码。

它包含以下内容:

addEventListener

cancelAnimationFrame

clearInterval

clearTimeout

console

Date

fetch

history

localStorage

location

navigator

open

random

removeEventListener

requestAnimationFrame

sessionStorage

setInterval

setTimeout

XMLHttpRequest

调试模式

Odoo 有时可以在称为 debug 模式的特殊模式下运行。它有两个主要用途:

  • 显示某些特定屏幕的附加信息/字段,

  • 提供一些额外的工具来帮助开发人员调试 Odoo 界面。

debug 模式由字符串描述。空字符串表示 debug 模式未激活。否则,它是活跃的。 如果字符串包含 assetstests,则激活相应的特定子模式(见下文)。两种模式可以同时激活,例如使用字符串 assets,tests

debug 模式当前值可以在 environment: env.debug 中读取。

小技巧

要仅在调试模式下显示菜单、字段或视图元素,您应该定位组 base.group_no_one

<field name="fname" groups="base.group_no_one"/>

资产模式

debug=assets 子模式对于调试 javascript 代码很有用:一旦激活,assets 包就不再缩小,并且还会生成源映射。这使得调试所有类型的 JavaScript 代码非常有用。

测试模式

还有另一个名为 tests 的子模式:如果启用,服务器会在页面中注入捆绑包 web.assets_tests。该捆绑包主要包含测试游览(其目的是测试功能,而不是向用户展示一些有趣的东西)。 tests 模式对于运行这些游览非常有用。

其他资料