服务¶
服务是长期运行的代码片段,用于提供特定功能。它们可以被组件(通过 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 |
|
显示图形效果 |
|
执行低级别 HTTP 调用 |
|
显示通知 |
|
管理浏览器网址 |
|
向服务器发送请求 |
|
处理对锚点元素的点击 |
|
标题 |
读取或修改窗口标题 |
提供与当前用户相关的某些信息 |
概览¶
技术名称:
cookie依赖项:无
提供一种操作 Cookie 的方法。例如:
cookieService.setCookie("hello", "odoo");
接口¶
- 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 的名称
删除 cookie
name。
效果服务¶
概览¶
技术名称:
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 与效果服务无关。
接口¶
- effectService.add(options)¶
- 参数
options (
object()) – 效果的选项。它们将传递给底层的效果组件。
显示一个效果。
选项由以下内容定义:
interface EffectOptions {
// The name of the desired effect
type?: string;
[paramName: string]: any;
}
可用效果¶
目前,唯一的效果是彩虹人。
彩虹人¶
effectService.add({ type: "rainbow_man" });
名称 |
类型 |
描述 |
|---|---|---|
|
|
在 RainbowMan 中实例化的组件类(将替换消息)。 |
|
|
如果提供了 params.Component,则可以通过此参数传递其属性。 |
|
|
消息是彩虹人持有的通知。 如果用户禁用了特效,彩虹人将不会出现,会显示一条简单的通知作为备用方案。 如果启用了效果且提供了 params.Component,则不使用 params.message。 消息是一个简单的字符串,或者是一个表示 HTML 的字符串(如果希望在 DOM 中有交互,请优先使用 params.Component)。 |
|
|
设置为 true 如果消息表示 HTML,以便它能正确插入到 DOM 中。 |
|
|
要显示在彩虹中的图片的网址。 |
|
|
彩虹人消失的延迟时间。
|
如何添加效果¶
效果存储在一个名为 effects 的注册表中。您可以通过提供名称和函数来添加新效果。
const effectRegistry = registry.category("effects");
effectRegistry.add("rainbow_man", rainbowManEffectFunction);
该函数必须遵循以下接口:
- <newEffectFunction>(env, params)¶
- 参数
env (
Env()) – 服务接收到的环境params (
object()) – 从服务的 add 函数接收到的参数。
- 返回
({组件, 属性} | 空)一个组件及其属性,或为空。
此函数必须创建一个组件并返回它。该组件将挂载在效果组件容器内。
示例¶
假设我们想添加一个效果,使页面呈现棕褐色调。
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" });
HTTP 服务¶
概览¶
技术名称:
http依赖项:无
在 Odoo 中,客户端与服务器之间的大多数交互都是 RPCs`(`XMLHTTPRequest),但在某些情况下可能需要对请求进行更底层的控制。
此服务提供了一种发送 get 和 post HTTP 请求 的方式。
接口¶
- async get(route[, readMethod = "json"])¶
- 参数
route (
string()) – 发送请求的网址readMethod (
string()) – 响应内容类型。可以是 “text”、”json”、”formData”、”blob”、”arrayBuffer”。
- 返回
请求的结果,格式由 readMethod 参数定义。
发送一个 GET 请求。
- async post(route[, params = {}, readMethod = "json"])¶
- 参数
route (
string()) – 发送请求的网址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");
接口¶
- add(message[, options])¶
- 参数
message (
string()) – 显示的通知消息options (
object()) – 通知的选项
- 返回
一个用于关闭通知的函数
显示一条通知。
选项由以下内容定义:
名称
类型
描述
标题字符串
为通知添加标题
类型警告|危险|成功|信息根据类型更改背景颜色
固定布尔值
是否应使通知一直显示到被关闭
类名字符串
附加的 CSS 类,该类将被添加到通知中
onClose函数
当通知关闭时要执行的回调函数
按钮按钮[](参见下文)
通知中要显示的按钮列表
自动关闭延迟数字
自动关闭通知前的持续时间(以毫秒为单位)
按钮由以下内容定义:
名称
类型
描述
名称字符串
按钮文本
点击函数
点击按钮时要执行的回调函数
主要布尔值
是否应将按钮样式设置为主要按钮
示例¶
当与客户达成销售交易时的通知,包含一个按钮,用于跳转到某种佣金页面。
// 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依赖项:无
router 服务提供三个功能:
当前路线的信息
应用根据其状态更新网址的一种方式
监听每次哈希变化,并通知应用程序的其余部分
接口¶
- current
当前路线可以通过
current键访问。它是一个包含以下信息的对象:pathname (字符串): 当前库位的路径(最可能是/web)search (object): 一个字典,将网址中的每个搜索关键词(查询字符串)映射到其对应的值。如果没有明确提供值,则值为空字符串。hash (对象): 与上述相同,但适用于哈希中描述的值。
例如:
// 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 方法更新网址:
- pushState(hash: object[, replace?: boolean])¶
- 参数
hash (
Object()) – 包含从某些键到某些值的映射的对象replace (
boolean()) – 如果为 true,则网址将被替换;否则,仅更新hash中的键/值对。
将
hash对象中的每个键/值对更新到网址中。如果某个值被设置为空字符串,则仅将该键添加到网址中,而不带任何对应的值。如果为 true,则
replace参数告诉路由器,应完全替换网址哈希(因此hash对象中不存在的值将被删除)。此方法调用不会重新加载页面。它也不会触发
hashchange事件,也不会在 主总线 中触发ROUTE_CHANGE事件。这是因为此方法仅用于更新网址。调用此方法的代码有责任确保屏幕也得到更新。
例如:
// 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 方法会将浏览器重定向到指定的网址:
- redirect(url[, wait])¶
- 参数
url (
string()) – 一个有效的网址wait (
boolean()) – 如果为 true,则等待服务器就绪,并在之后重定向
将浏览器重定向到
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 服务。
接口¶
- rpc(route, params, settings)¶
- 参数
route (
string()) – 被请求定向的路线params (
Object()) – (可选)发送到服务器的参数settings (
Object()) – (可选)请求设置(见下文)
settings对象可以包含:xhr,应为XMLHTTPRequest对象。在这种情况下,rpc方法将直接使用它,而不是创建一个新的对象。这在访问XMLHTTPRequest接口的高级功能时非常有用。silent (布尔值)如果设置为true,网页客户端将不会提供有关存在挂起的 rpc 的反馈。
该 rpc 服务通过使用 XMLHTTPRequest 对象与服务器进行通信,并配置为与 application/json 内容类型一起工作。因此,显然请求的内容应该是可序列化为 JSON 的。此服务发起的每个请求都使用 POST HTTP 方法。
服务器错误实际上会返回 HTTP 状态码 200 的响应。但 rpc 服务会将其视为错误。
错误处理¶
RPC 调用可能因以下两个主要原因而失败:
要么 Odoo 服务器返回一个错误(因此,我们称其为
服务器错误)。在这种情况下,HTTP 请求将以 HTTP 状态码 200 返回,但响应对象中会包含一个error键。或者有其他类型的网络错误
当 RPC 调用失败时,会发生以下情况:
如果代表 RPC 的 Promise 被拒绝,那么调用代码将崩溃,除非它处理了这种情况。
当主应用总线触发事件
RPC_ERROR时,事件负载中包含错误原因的描述:如果这是服务器错误(服务器代码抛出了异常)。在这种情况下,事件负载将是一个包含以下键的对象:
type = '服务器'消息(字符串)代码(数字)``name(string)``(可选,由错误服务使用,用于在处理错误时查找适当的对话框)
subType(string)(可选,通常用于确定对话框标题)data(object)(可选对象,可以包含多种键,其中debug:主要的调试信息,包括电话堆栈)
如果发生网络错误,则错误描述只是一个对象
{type: 'network'}。当发生网络错误时,会显示一个 通知,并且会定期联系服务器,直到其响应为止。一旦服务器响应,通知就会立即关闭。
滚动服务¶
概览¶
技术名称:
滚动器依赖项:无
每当用户在网页客户端中点击一个锚点时,此服务会自动滚动到目标位置(如果适用)。
该服务会添加一个事件监听器,用于捕获文档上的 click 事件。该服务会检查其 href 属性中包含的选择器是否有效,以区分锚点和 Odoo 操作(例如 <a href="#target_element"></a>)。如果不符合条件,则不做任何处理。
一个事件 SCROLLER:ANCHOR_LINK_CLICKED 会在主应用总线中被触发,如果点击似乎针对某个元素。该事件包含一个自定义事件,其中包含匹配的 element 和其 id 作为引用。这可能允许其他部分根据锚点本身处理某种行为。原始事件也会提供,因为它可能需要被阻止。如果该事件未被阻止,用户界面将滚动到目标元素。
接口¶
以下值包含在上面解释的 anchor-link-clicked 自定义事件中。
名称 |
类型 |
描述 |
|---|---|---|
|
|
被 href 定位的锚点元素 |
|
|
包含在 href 中的 id |
|
|
原始点击事件 |
注解
滚动器服务会在 主总线 上触发 SCROLLER:ANCHOR_LINK_CLICKED 事件。为了避免滚动器服务的默认滚动行为,您必须在监听器接收到的事件上使用 preventDefault(),以便从监听器中正确实现您自己的行为。
标题 服务¶
概览¶
技术名称:
标题依赖项:无
title 服务提供了一个简单的接口,用于读取和修改文档标题。例如,如果当前文档标题是 “Odoo”,我们可以使用以下命令将其更改为 “Odoo 15 - Apple”:
// in some component setup method
const titleService = useService("title");
titleService.setParts({ odoo: "Odoo 15", fruit: "Apple" });
接口¶
title 服务操作以下界面:
interface Parts {
[key: string]: string | null;
}
每个键代表标题中某一部分的身份,每个值是显示的字符串,如果该部分已被移除,则为 null。
它的接口是:
- current
这是一个表示当前标题的字符串。其结构如下:
value_1 - ... - value_n,其中每个value_i是在Parts对象中找到的(由getParts函数返回)非空值。
- getParts()¶
- 返回
当前由标题服务维护的
Parts对象的部分
- setParts(parts)¶
- 参数
parts (
Parts()) – 表示所需更改的对象
setParts方法允许添加/替换/删除标题的多个部分。删除一个部分(一个值)是通过将相关键的值设置为null来完成的。请注意,只能修改某一部分而不会影响其他部分。例如,如果标题由以下部分组成:
{ odoo: "Odoo", action: "Import" }
将
current值设为Odoo - 导入,然后setParts({ action: null, });
将标题更改为
Odoo。
用户服务¶
概览¶
技术名称:
user依赖项:
rpc
user 服务提供与已连接用户相关的数据和一些辅助函数。
接口¶
名称 |
类型 |
描述 |
|---|---|---|
|
|
用户上下文(user context) |
|
|
数据库相关信息 |
|
|
用户的主页所使用的动作的 ID |
|
|
是否为管理员(用户组 |
|
|
用户是否属于系统组( |
|
|
使用的语言 |
|
|
用户的名称 |
|
|
用户的业务伙伴实例的 ID |
|
|
用户的时区 |
|
|
用户的ID |
|
|
用户的别名 |
- 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")