框架概述?

介紹?

Odoo JavaScript 框架是由 web/ 插件提供的一組功能/構建塊,用于幫助構建在瀏覽器中運行的 Odoo 應用程序。同時,Odoo JavaScript 框架是一個單頁應用程序,通常稱為 web 客戶端 (可在 url /web 中使用)。

Web客戶端最初是使用自定義類和小部件系統創建的應用程序,但現在正在過渡到使用本地JavaScript類和Owl作為組件系統。這就解釋了為什么代碼庫中目前同時使用了這兩個系統。

從高層次的角度來看,Web客戶端是一個單頁應用程序:它不需要每次用戶執行操作時從服務器請求完整頁面。相反,它只請求所需內容,然后相應地替換/更新當前屏幕。此外,它管理URL以保持與當前狀態同步。

JavaScript 框架(全部或部分)也用于其他情況,例如 Odoo 網站或銷售點。本參考文檔主要關注于 Web 客戶端。

注解

在Odoo生態系統中,常見的詞匯 frontend * 和 * backend 被視為Odoo網站(公共)和Web客戶端的同義詞。這個術語不應與瀏覽器代碼(前端)和服務器(后端)的更常見用法混淆。

注解

在本文檔中,“組件”一詞始終指代新的Owl組件,“小部件”指代舊的Odoo小部件。

注解

如有可能,所有新的開發工作應該使用Owl完成!

代碼結構?

web/static/src 文件夾包含了所有 web/ 的 JavaScript(和 CSS 和模板)代碼庫。以下是最重要的文件夾列表:

  • core/ 大部分低級功能

  • fields/ 所有字段組件

  • views/ 所有 JavaScript 視圖組件( form , list ,…)

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

  • webclient/ 包含 Web 客戶端特定的代碼: 導航欄、用戶菜單、操作服務等。

web/static/src 是根目錄??梢允褂?@web 前綴導入其中的所有內容。例如,以下是如何導入位于 web/static/src/core/utils/functions 中的 memoize 函數的示例:

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

Web客戶端架構?

如上所述,Web客戶端是一個Owl應用程序。這是它的模板的一個稍微簡化的版本:

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

從我們可以看到,它基本上是一個導航欄、當前操作和一些附加組件的包裝器。 ActionContainer 是一個高階組件,它將顯示當前操作控制器(因此,是客戶端操作,或者在 act_window 類型的操作中是特定視圖)。管理操作是其工作的重要部分:操作服務在內存中保留所有活動操作的堆棧(在面包屑中表示),并協調每個更改。

另一個有趣的事情是 MainComponentsContainer :它只是一個組件,用于顯示在 main_components 注冊表中注冊的所有組件。這是其他系統部分擴展 Web 客戶端的方式。

環境?

作為Owl應用程序,Odoo Web客戶端定義了自己的環境(組件可以使用 this.env 訪問它)。以下是Odoo添加到共享 env 對象的描述:

qweb

Owl所需(包含所有模板)

bus

主總線 ,用于協調一些通用事件

services

所有已部署的 服務 (通常應使用 useService 鉤子訪問)

debug

string. 如果非空,則表示Web客戶端處于 debug mode 模式

_t

翻譯函數

isSmall

boolean。如果為真,則當前的 Web 客戶端處于移動模式(屏幕寬度 <= 767px)

例如,在組件中翻譯字符串(注意:模板會自動翻譯,因此在這種情況下不需要特定的操作),可以這樣做:

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

注解

擁有環境的引用非常強大,因為它提供了對所有服務的訪問。這在許多情況下都很有用:例如,用戶菜單項大多定義為字符串,并且一個以 env 為唯一參數的函數。這足以表達所有用戶菜單需求。

構建塊?

大部分的Web客戶端都是使用幾種抽象類型構建的:注冊表、服務、組件和鉤子。

注冊表?

Registries 基本上是一個簡單的鍵/值映射,用于存儲某種特定類型的對象。它們是UI可擴展性的重要組成部分:一旦某個對象被注冊,Web客戶端的其余部分就可以使用它。例如,字段注冊表包含可以在視圖中使用的所有字段組件(或小部件).

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

class MyFieldChar extends owl.Component {
    // some code
}

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

請注意,我們從 @web/core/registry 導入主注冊表,然后打開子注冊表 fields 。

服務?

Services 是長期存在的代碼塊,提供特定功能。它們可以被組件(使用 useService )或其他服務導入。此外,它們可以聲明一組依賴項。從這個意義上說,服務基本上是一個DI(依賴注入)系統。例如, notification 服務提供了一種顯示通知的方式,或者 rpc 服務是執行請求到Odoo服務器的正確方式。

以下示例注冊了一個簡單的服務,每5秒顯示一次通知:

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

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

serviceRegistry.add("myService", myService);

組件和鉤子?

組件鉤子 是來自于 Owl 組件系統 的概念。Odoo 組件就是 Web 客戶端中的 Owl 組件。

Hooks 是一種將代碼模塊化的方式,即使它依賴于生命周期。這是一種可組合/函數式的方式,可以在組件中注入功能。它們可以被看作是一種混合模式。

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 中的一個重要概念是 上下文 (context):它為代碼提供了一種在函數調用或 RPC 中提供更多上下文信息的方式,以便系統的其他部分可以正確地對該信息做出反應。在某種程度上,它就像是一個信息袋,無處不在地傳播。在某些情況下非常有用,例如讓 Odoo 服務器知道模型 RPC 來自特定的表單視圖,或在組件中激活/禁用某些功能。

在Odoo Web客戶端中有兩個不同的上下文: 用戶上下文操作上下文 (因此,在使用單詞 context 時,我們應該根據情況小心)。

注解

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()) – 評估上下文

返回

any[]

返回域的列表描述。請注意,此方法接受一個可選的 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') – 一個客服人員

返回

返回一個域,該域表示所有域的交集或并集,具體取決于operator參數的值。

總線?

Web客戶端 環境 對象包含一個事件總線,名為 bus 。它的目的是允許系統的各個部分適當地協調自己,而不是耦合它們。 env.bus 是一個 owl EventBus <https://github.com/odoo/owl/blob/master/doc/reference/event_bus.md> ,應該用于感興趣的全局事件。

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

以下是可以在此總線上觸發的事件列表:

消息

有效載荷

觸發器

ACTION_MANAGER:UI-UPDATED

表示 UI 的哪個部分已更新的模式(’current’、’new’ 或 ‘fullscreen’)

已完成對動作管理器請求的渲染

ACTION_MANAGER:UPDATE

下一個渲染信息

操作管理器已完成計算下一個界面

MENUS:APP-CHANGED

菜單服務的當前應用已更改

ROUTE_CHANGE

URL哈希已更改

RPC:REQUEST

RPC ID

一個 RPC 請求剛剛開始

RPC:REQUEST

RPC ID

一個 RPC 請求已完成

WEB_CLIENT_READY

Web客戶端已掛載

FOCUS-VIEW

主視圖應該自我聚焦

CLEAR-CACHES

應清除所有內部緩存

CLEAR-UNCOMMITTED-CHANGES

函數列表

所有具有未提交更改的視圖都應該清除它們,并將回調推送到列表中

瀏覽器對象?

javascript框架還提供了一個特殊的對象 browser ,它提供了許多瀏覽器API的訪問,如 location , localStoragesetTimeout 。例如,以下是如何使用 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 模式未激活。否則,它是激活的。如果字符串包含 assetstests ,則相應的特定子模式被激活(見下文)。兩種模式可以同時激活,例如使用字符串 assets,tests 。

可以在 環境 中讀取 env.debug 來獲取當前的 debug 模式值。

小技巧

如果想要在調試模式下僅顯示菜單、字段或視圖元素,您應該將其定向到 base.group_no_one 組:

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

另請參閱

資源模式?

debug=assets 子模式對于調試 JavaScript 代碼非常有用:一旦激活, assets 捆綁包不再被壓縮,同時也會生成源映射。這使得它非常適合調試各種 JavaScript 代碼。

測試模式?

還有另一個名為 tests 的子模式:如果啟用,服務器會在頁面中注入 web.assets_tests 包。該包主要包含測試游覽(tours),即旨在測試功能而不是向用戶展示有趣內容的游覽。 tests 模式對于能夠運行這些游覽非常有用。

另請參閱