服務?

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

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

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

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

registry.category("services").add("myService", myService);

在啟動時,Web 客戶端會啟動 services 注冊表中存在的所有服務。請注意,注冊表中使用的名稱是服務的名稱。

注解

大多數不是組件的代碼應該 打包 在服務中,特別是如果它執行了一些副作用。這對于測試非常有用:測試可以選擇哪些服務是活動的,因此減少了不希望的副作用干擾被測試的代碼的機會。

定義服務?

一個服務需要實現以下接口:

dependencies?

可選字符串列表。這是該服務需要的所有依賴項(其他服務)的列表。

start(env, deps)?
參數
  • env (Environment()) – 應用程序環境

  • deps (Object()) – 所有請求的依賴項

返回

服務的值或Promise<服務的值>

這是服務的主要定義。它可以返回值或者一個 Promise。在這種情況下,服務加載器會等待 Promise 解析為一個值,這個值就是服務的值。

有些服務不導出任何值。它們可能只是在不需要被其他代碼直接調用的情況下完成它們的工作。在這種情況下,它們的值將在 env.services 中設置為 null 。

async?

可選值。如果提供,應為 true 或字符串列表。

一些服務需要提供異步API。例如, rpc 服務是一個異步函數,或者 orm 服務提供了一組函數來調用Odoo服務器。

在這種情況下,使用服務的組件可能會在異步函數調用結束之前被銷毀。大多數情況下,異步函數調用需要被忽略。否則,這可能非常危險,因為底層組件不再處于活動狀態。 async 標志是一種方法:它向服務創建者發出信號,如果組件被銷毀,則應將來自組件的所有異步調用保留為待處理狀態。

使用服務?

一個依賴于其他服務并已正確聲明其 dependencies 的服務,只需在 start 方法的第二個參數中接收對應服務的引用即可。

useService 鉤子是在組件中使用服務的正確方式。它只是返回對服務值的引用,稍后可以由組件使用。例如:

import { useService } from "@web/core/utils/hooks";

class MyComponent extends Component {
  setup() {
    const rpc = useService("rpc");

    onWillStart(async () => {
      this.someValue = await rpc(...);
    });
  }
}

參考列表?

技術名稱

簡短描述

cookie

讀取或修改 cookies

effect

顯示圖形效果

http

執行低級別的http調用

通知

顯示通知

路由器

管理瀏覽器URL

rpc

向服務器發送請求

滾動條

處理錨元素上的點擊事件

title

讀取或修改窗口標題

user

提供與當前用戶相關的一些信息

概覽?

  • 技術名稱: cookie

  • 依賴項:無

提供一種操作 cookie 的方式。例如:

cookieService.setCookie("hello", "odoo");

API?

current?

表示每個cookie及其值(如果有)的對象(或空字符串)

setCookie(name[, value, ttl])?
參數
  • name (string()) – 應設置的 cookie 的名稱

  • value (any()) – 可選。如果提供了該值,則 cookie 將被設置為該值

  • ttl (number()) – 可選。cookie將被刪除之前的時間(以秒為單位)(默認值=1年)

將cookie的名稱設置為 name ,值為 value ,最大生存時間為 ttl

deleteCookie(name)?
參數
  • name (string()) – cookie的名稱

刪除名為 name 的 cookie。

特效服務?

概覽?

  • 技術名稱: effect

  • 依賴項:無

效果是可以在頁面頂部臨時顯示的圖形元素,通常用于向用戶提供反饋,表明發生了一些有趣的事情。

一個好的例子是彩虹人:

彩虹人效應

這是如何顯示的:

const effectService = useService("effect");
effectService.add({
  type: "rainbow_man", // can be omitted, default type is already "rainbow_man"
  message: "Boom! Team record for the past 30 days.",
});

警告

鉤子 useEffect 與 effect 服務無關。

API?

effectService.add(options)?
參數
  • options (object()) – 效果的選項。它們將被傳遞到底層的效果組件。

顯示一個效果。

選項由以下定義:

interface EffectOptions {
  // The name of the desired effect
  type?: string;
  [paramName: string]: any;
}

可用效果?

目前,唯一的效果是彩虹人。

彩虹人?
effectService.add({ type: "rainbow_man" });

名稱

類型

描述

params.Component

owl.Component?

在 RainbowMan 內實例化的組件類(將替換消息)。

params.props

object?={}

如果給定了params.Component,則可以使用此參數傳遞其props。

params.message

string?="干得好!"

消息是彩虹人手中的通知。

如果用戶禁用了效果,則彩虹人將不會出現,而是顯示一個簡單的通知作為備選方案。

如果啟用了效果并且給出了params.Component,則不使用params.message。

消息是一個簡單的字符串或表示HTML的字符串(如果您想在DOM中進行交互,請優先使用params.Component)。

params.messageIsHtml

boolean?=false

如果消息表示HTML,則設置為true,以便正確插入到DOM中。

params.img_url

string?=/web/static/img/smile.svg

彩虹內顯示的圖像的URL。

params.fadeout

("slow"|"medium"|"fast"|"no")?="medium"

彩虹人消失的延遲時間。

"fast" 會讓彩虹人快速消失。

"medium""slow" 會在消失之前等待更長的時間(當 params.message 較長時可以使用)。

"no" 將使得彩虹人一直停留在屏幕上,直到用戶在彩虹人外的任何地方點擊。

如何添加特效?

特效存儲在名為 effects 的注冊表中。您可以通過提供名稱和函數來添加新的特效。

const effectRegistry = registry.category("effects");
effectRegistry.add("rainbow_man", rainbowManEffectFunction);

該函數必須遵循此 API:

<newEffectFunction>(env, params)?
參數
  • env (Env()) – 服務接收到的環境

  • params (object()) – 從服務的 add 函數接收到的參數。

返回

({Component, props} | void) 一個組件及其屬性或者什么都沒有。

此函數必須創建一個組件并返回它。該組件將被掛載在效果組件容器內部。

示例?

假設我們想要添加一個效果,使頁面呈現出棕褐色的外觀。

/** @odoo-module **/

import { registry } from "@web/core/registry";
const { Component, tags } = owl;

class SepiaEffect extends Component {}
SepiaEffect.template = tags.xml`
    <div style="
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        background: rgba(124,87,0, 0.4);
    "></div>
`;

export function sepiaEffectProvider(env, params = {}) {
    return {
        Component: SepiaEffect,
    };
}

const effectRegistry = registry.category("effects");
effectRegistry.add("sepia", sepiaEffectProvider);

然后,在您想要的任何地方調用它,您將看到結果。在這里,它在webclient.js中被調用,以使其在示例中隨處可見。

const effectService = useService("effect");
effectService.add({ type: "sepia" });
Odoo在棕褐色中

HTTP服務?

概覽?

  • 技術名稱: http

  • 依賴項:無

雖然在odoo中客戶端和服務器之間的大多數交互是通過 RPCsXMLHTTPRequest )實現的,但有時可能需要更低級別的請求控制。

該服務提供了一種發送 getpost http請求<https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods> 的方式。

API?

async get(route[, readMethod = "json"])?
參數
  • route (string()) – 發送請求的URL地址

  • readMethod (string()) – 響應內容類型??梢允?“text”、”json”、”formData”、”blob”、”arrayBuffer”。

返回

使用readMethod參數定義的格式返回請求結果。

發送一個 GET 請求。

async post(route[, params = {}, readMethod = "json"])?
參數
  • route (string()) – 發送請求的URL地址

  • params (object()) – 要設置在請求表單數據部分的鍵值數據

  • readMethod (string()) – 響應內容類型??梢允?“text”、”json”、”formData”、”blob”、”arrayBuffer”。

返回

使用readMethod參數定義的格式返回請求結果。

發送一個POST請求。

示例?

const httpService = useService("http");
const data = await httpService.get("https://something.com/posts/1");
// ...
await httpService.post("https://something.com/posts/1", { title: "new title", content: "new content" });

通知服務?

概覽?

  • 技術名稱: notification

  • 依賴項:無

notification 服務允許在屏幕上顯示通知。

const notificationService = useService("notification");
notificationService.add("I'm a very simple notification");

API?

add(message[, options])?
參數
  • message (string()) – 要顯示的通知消息

  • options (object()) – 通知的選項

返回

關閉通知的函數

顯示通知。

選項由以下定義:

名稱

類型

描述

title

字符串

為通知添加標題

type

warning | danger | success | info

根據類型更改背景顏色

sticky

布爾值

通知是否應該一直保留直到被解除

className

字符串

將添加到通知中的附加 CSS 類

onClose

函數

當通知關閉時執行的回調函數

buttons

button[](見下文)

在通知中顯示的按鈕列表

按鈕的定義如下:

名稱

類型

描述

name

字符串

按鈕文本

onClick

函數

當按鈕被點擊時執行的回調函數

primary

布爾值

按鈕是否應該被設計成主要按鈕

示例?

當銷售交易完成時,通知用戶并提供一個按鈕,以便跳轉到某種傭金頁面。

// in setup
this.notificationService = useService("notification");
this.actionService = useService("action");

// later
this.notificationService.add("You closed a deal!", {
  title: "Congrats",
  type: "success",
  buttons: [
      {
          name: "See your Commission",
          onClick: () => {
              this.actionService.doAction("commission_action");
          },
      },
  ],
});
通知示例

一秒鐘后自動關閉的通知:

const notificationService = useService("notification");
const close = notificationService.add("I will be quickly closed");
setTimeout(close, 1000);

路由器服務?

概覽?

  • 技術名稱: 路由器

  • 依賴項:無

“router”服務提供三個功能:

  • 當前路由的信息

  • 一種讓應用程序根據其狀態更新URL的方法

  • 監聽每個哈希變化,并通知應用程序的其余部分

API?

current

當前路由可以通過 current 鍵訪問。它是一個包含以下信息的對象:

  • pathname (字符串) : 當前位置的路徑(很可能是 /web )

  • search (object) : 一個字典,將 URL 中的每個搜索關鍵字(查詢字符串)映射到其值。如果沒有明確給出值,則為空字符串。

  • hash (object) : 與上面相同,但是針對哈希中描述的值。

例如:

// url = /web?debug=assets#action=123&owl&menu_id=174
const { pathname, search, hash } = env.services.router.current;
console.log(pathname); //   /web
console.log(search); //   { debug="assets" }
console.log(hash); //   { action:123, owl: "", menu_id: 174 }

使用 pushState 方法更新 URL:

pushState(hash: object[, replace?: boolean])?
參數
  • hash (Object()) – 包含一些鍵值對映射的對象

  • replace (boolean()) – 如果為 true,則會替換 URL,否則僅更新哈希中的鍵/值對。

使用 hash 對象中的每個鍵/值對更新URL。如果將值設置為空字符串,則添加鍵到URL中,而沒有任何相應的值。

如果為真,則 replace 參數告訴路由器應該完全替換 URL 哈希(因此 hash 對象中不存在的值將被刪除)。

該方法調用不會重新加載頁面。它也不會觸發 hashchange 事件,也不會在 主總線 中觸發 ROUTE_CHANGE 事件。這是因為該方法僅用于更新URL。調用該方法的代碼有責任確保屏幕也得到更新。

例如:

// url = /web#action_id=123
routerService.pushState({ menu_id: 321 });
// url is now /web#action_id=123&menu_id=321
routerService.pushState({ yipyip: "" }, replace: true);
// url is now /web#yipyip

最后, redirect 方法將會把瀏覽器重定向到指定的 URL:

redirect(url[, wait])?
參數
  • url (string()) – 一個有效的URL

  • wait (boolean()) – 如果為真,則等待服務器準備就緒,然后重定向

將瀏覽器重定向到 url 。此方法會重新加載頁面。 wait 參數很少使用:它在某些情況下很有用,例如我們知道服務器在短時間內將不可用,通常是在插件更新或安裝操作之后。

注解

每當當前路由發生變化時,路由服務會在 主總線 上發出 ROUTE_CHANGE 事件。

RPC服務?

概覽?

  • 技術名稱: rpc

  • 依賴項:無

rpc 服務提供了一個異步函數來向服務器發送請求。調用控制器非常簡單:路由應該是第一個參數,可選地,可以將 params 對象作為第二個參數給出。

// in setup
this.rpc = useService("rpc");

// somewhere else, in an async function:
const result = await this.rpc("/my/route", { some: "value" });

注解

請注意, rpc 服務被認為是低級服務。它只應用于與Odoo控制器交互。要使用模型(這是迄今為止最重要的用例)工作,應改用 orm 服務。

API?

rpc(route, params, settings)?
參數
  • route (string()) – 請求所針對的路由

  • params (Object()) – 發送到服務器的參數

  • (optional) (Object settings()) – 請求設置(見下文)

settings 對象可以包含以下內容:

  • xhr ,應該是一個 XMLHTTPRequest 對象。在這種情況下, rpc 方法將直接使用它而不是創建一個新的對象。當訪問 XMLHTTPRequest API的高級功能時,這是非常有用的。

  • silent (boolean) 如果設置為 true ,Web 客戶端將不會提供有待處理的 RPC 的反饋。

rpc 服務通過使用配置為與 application/json 內容類型一起工作的 XMLHTTPRequest 對象與服務器通信。因此,請求的內容應該是可 JSON 序列化的。此服務執行的每個請求都使用 POST http 方法。

服務器錯誤實際上會返回一個 HTTP 200 響應代碼。但是 rpc 服務會將其視為錯誤。

錯誤處理?

RPC 失敗的主要原因有兩個:

  • 如果Odoo服務器返回錯誤(因此,我們將其稱為“服務器”錯誤),則HTTP請求將返回HTTP代碼200,但是響應對象將包含一個“error”鍵。

  • 或者存在其他類型的網絡錯誤

當 rpc 失敗時,那么:

  • 表示RPC的Promise被拒絕,因此調用代碼將崩潰,除非它處理這種情況

  • 當發生錯誤時,主應用程序總線會觸發一個名為“RPC_ERROR”的事件。事件負載包含錯誤原因的描述:

    如果是服務器錯誤(服務器代碼拋出異常),則事件負載將是一個具有以下鍵的對象:

    • type = 'server'

    • message(string)

    • code(number)

    • name(string) (可選項,用于錯誤服務查找適當的對話框以處理錯誤)

    • subType(string) (可選項,通常用于確定對話框標題)

    • data(object) (可選對象,可以包含各種鍵,其中包括 debug : 主要的調試信息,包括調用堆棧)

    如果是網絡錯誤,則錯誤描述僅為一個對象 {type: 'network'} 。當發生網絡錯誤時,會顯示一個 notification ,并定期聯系服務器直到它響應。一旦服務器響應,通知就會關閉。

滾動條服務?

概覽?

  • 技術名稱: scroller

  • 依賴項:無

每當用戶在Web客戶端中單擊錨點時,此服務會自動滾動到目標位置(如果適用)。

該服務添加了一個事件監聽器,以獲取文檔上的 click 事件。該服務檢查其 href 屬性中包含的選擇器是否有效,以區分錨點和 Odoo 操作(例如 <a href="#target_element"></a> )。如果不是這種情況,則不執行任何操作。

如果單擊似乎針對某個元素,則在主應用程序總線上觸發事件 SCROLLER:ANCHOR_LINK_CLICKED 。該事件包含一個自定義事件,其中包含與其 id 相匹配的 element 作為引用。它可能允許其他部分處理與錨點本身相關的行為。原始事件也會被給出,因為它可能需要被阻止。如果未阻止事件,則用戶界面將滾動到目標元素。

API?

下面的值包含在上面解釋的 anchor-link-clicked 自定義事件中。

名稱

類型

描述

element

HTMLElement | null

被 href 定位的錨點元素

id

string

包含在 href 中的 ID

originalEv

Event

原始點擊事件

注解

滾動條服務在 主總線 上發出 SCROLLER:ANCHOR_LINK_CLICKED 事件。為了避免滾動條服務的默認滾動行為,您必須在監聽器中使用 preventDefault() 函數來阻止事件的默認行為,以便您可以從監聽器中正確地實現自己的行為。

標題服務?

概覽?

  • 技術名稱: title

  • 依賴項:無

title 服務提供了一個簡單的API,允許讀取/修改文檔標題。例如,如果當前文檔標題為“Odoo”,我們可以使用以下命令將其更改為“Odoo 15 - Apple”:

// in some component setup method
const titleService = useService("title");

titleService.setParts({ odoo: "Odoo 15", fruit: "Apple" });

API?

title 服務操作以下接口:

interface Parts {
    [key: string]: string | null;
}

每個鍵表示標題的一部分的標識,每個值是顯示的字符串,如果已刪除,則為 null 。

它的 API 是:

current

這是一個表示當前標題的字符串。它的結構如下: value_1 - ... - value_n ,其中每個 value_i 是在 getParts 函數返回的 Parts 對象中找到的(非空)值。

getParts()?
返回

分割當前由標題服務維護的 Parts 對象

setParts(parts)?
參數
  • parts (Parts()) – 表示所需更改的對象

setParts 方法允許添加/替換/刪除標題的多個部分。通過將關聯的鍵值設置為 null 來刪除一個部分(一個值)。

請注意,只能修改單個部分而不影響其他部分。例如,如果標題由以下部分組成:

{ odoo: "Odoo", action: "Import" }

如果 current 的值為 Odoo - Import ,那么

setParts({
  action: null,
});

將標題更改為 Odoo 。

用戶服務?

概覽?

  • 技術名稱: user

  • 依賴項: rpc

user 服務提供了有關連接用戶的一些數據和幾個輔助函數。

API?

名稱

類型

描述

context

Object

用戶上下文: 用戶上下文

db

Object

數據庫信息

home_action_id

(number | false)

用戶主頁所使用的操作的ID

isAdmin

boolean

用戶是否是管理員(組 base.group_erp_manager 或超級用戶)

isSystem

boolean

用戶是否屬于系統組 ( base.group_system )

lang

string

使用的語言

name

string

用戶姓名

partnerId

number

用戶的合作伙伴實例ID

tz

string

用戶的時區

userId

number

用戶ID

userName

string

用戶的替代昵稱

updateContext(update)?
參數
  • update (object()) – 用于更新上下文的對象

使用給定對象更新 用戶上下文 。

userService.updateContext({ isFriend: true })
removeFromContext(key)?
參數
  • key (string()) – 目標屬性的鍵

用戶上下文 中刪除給定鍵的值

userService.removeFromContext("isFriend")
hasGroup(group)?
參數
  • group (string()) – 要查找的組的xml_id

返回

Promise<boolean> 用戶是否在該組中

檢查用戶是否屬于某個組

const isInSalesGroup = await userService.hasGroup("sale.group_sales")