服务¶
服务是提供某项功能的长期存在的代码片段。它们可以被组件(通过 useService)或其他服务导入。此外,它们还可以声明一组依赖项。从这个意义上说,服务基本上就是一个 DI dependency injection 系统。例如,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 { rpc } from "@web/core/network/rpc";
class MyComponent extends Component {
setup() {
onWillStart(async () => {
const result = await rpc(...);
})
}
}
参考列表¶
技术名称 |
简短说明 |
|---|---|
读取或修改 cookie |
|
显示图形特效 |
|
执行底层 http 调用 |
|
显示通知 |
|
管理浏览器 URL |
|
向服务器发送请求 |
|
处理锚点元素上的点击 |
|
读取或修改窗口标题 |
|
提供与当前用户相关的一些信息 |
概述¶
技术名称:
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 的名称
删除 cookie
name。
Effect 服务¶
概述¶
技术名称:
effect依赖项:无
特效是可在页面顶部临时显示的图形元素,通常用于向用户反馈发生了某些有趣的事情。
彩虹人(rainbow man)就是一个很好的例子:
下面说明如何显示它:
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 钩子与特效服务无关。
API¶
- effectService.add(options)¶
- 参数
options (
object()) – 特效的选项。它们会传递给底层的特效组件。
显示一个特效。
选项定义如下:
interface EffectOptions {
// The name of the desired effect
type?: string;
[paramName: string]: any;
}
可用特效¶
目前唯一的特效是彩虹人。
RainbowMan¶
effectService.add({ type: "rainbow_man" });
名称 |
类型 |
说明 |
|---|---|---|
|
|
要在 RainbowMan 内部实例化的组件类(将替换消息)。 |
|
|
如果提供了 params.Component,则可通过此参数传递其 props。 |
|
|
消息是彩虹人显示的通知内容。 如果为该用户禁用了特效,则不会出现彩虹人,而是改为显示一条简单的通知。 如果启用了特效且提供了 params.Component,则不使用 params.message。 消息是简单字符串或表示 html 的字符串(如果希望在 DOM 中进行交互,建议使用 params.Component)。 |
|
|
如果消息表示 html,则设为 true,以便将其正确插入 DOM。 |
|
|
在彩虹图形内部显示的图像 URL。 |
|
|
彩虹人消失的延迟时间。
|
如何添加特效¶
特效存储在名为 effects 的注册表中。你可以通过提供名称和函数来添加新特效。
const effectRegistry = registry.category("effects");
effectRegistry.add("rainbow_man", rainbowManEffectFunction);
该函数必须遵循以下 API:
- <newEffectFunction>(env, params)¶
- 参数
env (
Env()) – 服务接收的环境params (
object()) – 从服务的 add 函数接收的 params。
- 退货
({Component, props} | void)一个组件及其 props,或什么都不返回。
该函数必须创建一个组件并将其返回。该组件会挂载到特效组件容器内部。
示例¶
假设我们要添加一个为页面增加棕褐色调的特效。
import { registry } from "@web/core/registry";
import { Component, xml } from "@odoo/owl";
class SepiaEffect extends Component {
static template = 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 requests 的方法。
API¶
- async get(route[, readMethod = "json"])¶
- 参数
route (
string()) – 发送请求的目标 URLreadMethod (
string()) – 响应的内容类型。可以是 “text”、”json”、”formData”、”blob”、”arrayBuffer”。
- 退货
按 readMethod 参数定义的格式返回的请求结果。
发送 get 请求。
- async post(route[, params = {}, readMethod = "json"])¶
- 参数
route (
string()) – 发送请求的目标 URLparams (
object()) – 要在请求的 form data 部分中设置的键值数据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依赖项:无
notification 服务允许在屏幕上显示通知。
const notificationService = useService("notification");
notificationService.add("I'm a very simple notification");
API¶
- add(message[, options])¶
- 参数
message (
string()) – 要显示的通知消息options (
object()) – 通知的选项
- 退货
用于关闭通知的函数
显示一条通知。
选项定义如下:
名称
类型
说明
titlestring
为通知添加标题
typewarning|danger|success|info根据类型更改背景颜色
sticky布尔值
通知是否应保持显示直到被关闭
classNamestring
将添加到通知的额外 css 类
onClosefunction
通知关闭时执行的回调
buttonsbutton[](见下文)
要在通知中显示的按钮列表
autocloseDelay数字
通知自动关闭前的持续时间(毫秒)
按钮定义如下:
名称
类型
说明
namestring
按钮文本
onClickfunction
按钮被点击时执行的回调
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依赖项:无
router 服务提供三项功能:
有关当前路由的信息
一种让应用程序根据自身状态更新 url 的方式
监听每次 hash 变化,并通知应用程序的其余部分
API¶
- current
当前路由可通过
current键访问。它是一个包含以下信息的对象:pathname (string):当前位置的路径(很可能是/web)search (object):一个字典,将 url 中的每个搜索关键字(查询字符串)映射到其值。如果未显式给出值,则值为空字符串hash (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 }
更新 URL 通过 pushState 方法完成:
- pushState(hash: object[, replace?: boolean])¶
- 参数
hash (
Object()) – 包含从某些键到某些值的映射的对象replace (
boolean()) – 如果为 true,则替换 url,否则只更新hash中的键/值对。
使用
hash对象中的每个键/值对更新 URL。如果将某个值设为空字符串,则该键会添加到 url 中,而不带任何对应的值。如果为 true,
replace参数会告知路由器 url hash 应被完全替换(因此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()) – 一个有效的 urlwait (
boolean()) – 如果为 true,则等待服务器就绪后再重定向
将浏览器重定向到
url。此方法会重新加载页面。wait参数很少使用:在某些情况下很有用,例如我们知道服务器将在短时间内不可用,通常是在插件模块更新或安装操作之后。
注解
每当当前路由发生变化时,路由服务都会在 主总线 上发出 ROUTE_CHANGE 事件。
RPC 服务¶
概述¶
技术名称:
rpc依赖项:无
rpc 服务提供一个异步函数来向服务器发送请求。调用控制器非常简单:路由应作为第一个参数,可选地,可以将 params 对象作为第二个参数传入。
import { rpc } from "@web/core/network/rpc";
// somewhere else, in an async function:
const result = await rpc("/my/route", { some: "value" });
注解
请注意,rpc 服务被视为低层服务。它只应用于与 Odoo 控制器交互。要处理模型(这是目前最重要的用例),应改用 orm 服务。
API¶
- rpc(route, params, settings)¶
- 参数
route (
string()) – 请求所指向的路由params (
Object()) – (可选)发送到服务器的参数settings (
Object()) – (可选)请求设置(见下文)
settings对象可以包含:xhr,它应是一个XMLHTTPRequest对象。在这种情况下,rpc方法将直接使用它,而不是创建新的对象。这在访问XMLHTTPRequestAPI 的高级特性时很有用。silent (boolean)如果设为true,Web 客户端将不会提供有待处理 rpc 的反馈。
rpc 服务通过使用配置为 application/json 内容类型的 XMLHTTPRequest 对象与服务器通信。因此,请求内容显然应可 JSON 序列化。此服务发出的每个请求都使用 POST http 方法。
服务器错误实际上会以 http 状态码 200 返回响应。但 rpc 服务会将其视为错误。
错误处理¶
rpc 可能因两个主要原因而失败:
要么 odoo 服务器返回错误(我们称之为
server错误)。在这种情况下,http 请求将以 http 状态码 200 返回,但响应对象中包含error键。要么是其他某种网络错误
当 rpc 失败时:
表示该 rpc 的 promise 被拒绝,因此调用代码会崩溃,除非它处理了这种情况
在主应用程序总线上触发一个
RPC_ERROR事件。事件负载包含错误原因的描述:如果是服务器错误(服务器代码抛出了异常)。在这种情况下,事件负载将是一个包含以下键的对象:
type = 'server'message(string)code(number)name(string)(可选,错误服务用它来查找处理错误时要使用的合适对话框)subType(string)(可选,常用于确定对话框标题)data(object)(可选对象,可包含多种键,其中包括debug:主要调试信息,含调用栈)
如果是网络错误,那么错误描述只是一个对象
{type: 'network'}。发生网络错误时,会显示一个 通知,并定期联系服务器,直到它响应。服务器一旦响应,通知就会关闭。
滚动服务¶
概述¶
技术名称:
scroller依赖项:无
每当用户在 Web 客户端中点击锚点时,此服务会自动滚动到目标位置(如果适用)。
该服务添加一个事件监听器以捕获文档上的 click。该服务检查其 href 属性中包含的选择器是否有效,以区分锚点和 Odoo 动作(例如 <a href="#target_element"></a>)。如果不是这种情况,则不做任何操作。
如果点击看起来是针对某个元素的,则在主应用程序总线上触发 SCROLLER:ANCHOR_LINK_CLICKED 事件。该事件包含一个自定义事件,其中带有匹配的 element 及其 id 作为引用。它可能允许其他部分处理与锚点自身相关的行为。原始事件也会一并提供,因为它可能需要被阻止。如果事件未被阻止,则用户界面将滚动到目标元素。
API¶
上面说明的 anchor-link-clicked 自定义事件包含以下值。
名称 |
类型 |
说明 |
|---|---|---|
|
|
href 所指向的锚点元素 |
|
|
href 中包含的 id |
|
|
原始的点击事件 |
注解
滚动服务会在 主总线 上发出 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都是Parts对象中(由getParts函数返回)的一个非空值
- getParts()¶
- 退货
Parts:标题服务当前维护的
Parts对象
- setParts(parts)¶
- 参数
parts (
Parts()) – 表示所需更改的对象
setParts方法允许添加/替换/删除标题的多个部分。删除某个部分(某个值)的方法是将其关联的键值设为null。请注意,可以只修改单个部分而不影响其他部分。例如,如果标题由以下部分组成:
{ odoo: "Odoo", action: "Import" }
如果
current值为Odoo - Import,则setParts({ action: null, });
将把标题改为
Odoo。
用户服务¶
概述¶
技术名称:
user依赖:
rpc
user 服务提供一系列数据和几个与已连接用户有关的辅助函数。
API¶
名称 |
类型 |
说明 |
|---|---|---|
|
|
|
|
|
有关数据库的信息 |
|
|
用作该用户主页的动作 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")