服务¶
服务是提供功能的长期存在的代码片段。它们可以被组件(使用 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
方法的第二个参数中接收对应服务的引用即可。
The useService
hook is the proper way to use a service in a component. It
simply returns a reference to the service value, that can then be used by the
component later. For example:
import { useService } from "@web/core/utils/hooks";
class MyComponent extends Component {
setup() {
const rpc = useService("rpc");
onWillStart(async () => {
this.someValue = await rpc(...);
});
}
}
参考列表¶
技术名称 |
简短描述 |
---|---|
读取或修改 cookies |
|
显示图形效果 |
|
执行低级别的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
依赖项:无
效果是可以在页面顶部临时显示的图形元素,通常用于向用户提供反馈,表明发生了一些有趣的事情。
一个好的例子是彩虹人:
这是如何显示的:
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" });
名称 |
类型 |
描述 |
---|---|---|
|
|
在 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 函数接收到的参数。
- 返回
({Component, props} | void)
A component and its props or nothing.
此函数必须创建一个组件并返回它。该组件将被挂载在效果组件容器内部。
示例¶
假设我们想要添加一个效果,使页面呈现出棕褐色的外观。
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
依赖项:无
While most interactions between the client and the server in odoo are RPCs
(XMLHTTPRequest
), lower level
control on requests may sometimes be required.
该服务提供了一种发送 get
和 post
http 请求 的方式。
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
依赖项:无
The notification
service allows to display notifications on the screen.
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[](见下文)
在通知中显示的按钮列表
autocloseDelay
数字
duration in milliseconds before the notification is closed automatically
按钮的定义如下:
名称
类型
描述
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
依赖项:无
The router
服务提供三个功能:
当前路由的信息
一种让应用程序根据其状态更新URL的方法
监听每个哈希变化,并通知应用程序的其余部分
API¶
- current
当前路由可以通过
current
键访问。它是一个包含以下信息的对象:pathname (string)
: 当前位置的路径(最有可能是/web
)search (object)
: a dictionary mapping each search keyword (the querystring) from the url to its value. An empty string is the value if no value was explicitely givenhash (object)
: same as above, but for values described in the 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
方法更新 URL:
- pushState(hash: object[, replace?: boolean])¶
- 参数
hash (
Object()
) – 包含一些键值对映射的对象replace (
boolean()
) – 如果为真,则替换URL,否则仅更新hash
中的键/值对。
使用
hash
对象中的每个键/值对更新URL。如果将值设置为空字符串,则添加键到URL中,而没有任何相应的值。如果为真,则
replace
参数告诉路由器应该完全替换 URL 哈希(因此hash
对象中不存在的值将被删除)。此方法调用不会重新加载页面。它也不会触发
hashchange
事件,也不会在 main bus 中触发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()
) – 如果为真,则等待服务器准备就绪,然后重定向
重定向浏览器到
url
。此方法重新加载页面。wait
参数很少使用:它在某些情况下很有用,例如我们知道服务器将在短时间内不可用,通常是在插件更新或安装操作之后。
注解
每当当前路由发生变化时,路由服务会在 主总线 上发出 ROUTE_CHANGE
事件。
RPC服务¶
概览¶
技术名称:
rpc
依赖项:无
The rpc
service provides a single asynchronous function to send requests to
the server. Calling a controller is very simple: the route should be the first
argument and optionally, a params
object can be given as a second argument.
// 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()
) – (可选)发送到服务器的参数settings (
Object()
) – (可选)请求设置(见下文)
The
settings
object can contain:xhr
, which should be aXMLHTTPRequest
object. In that case, therpc
method will simply use it instead of creating a new one. This is useful when one accesses advanced features of theXMLHTTPRequest
API.silent (boolean)
如果设置为true
,Web 客户端将不会提供有待处理的 RPC 的反馈。
The rpc
service communicates with the server by using a XMLHTTPRequest
object, configured to work with the application/json
content type. So clearly
the content of the request should be JSON serializable. Each request done by
this service uses the POST
http method.
服务器错误实际上会返回一个 HTTP 200 响应代码。但是 rpc
服务会将其视为错误。
错误处理¶
RPC 失败的主要原因有两个:
无论是odoo服务器返回错误(所以我们称之为
server
错误)。在这种情况下,http请求将返回一个http代码200,但是返回的响应对象中包含一个error
键。或者存在其他类型的网络错误
当 rpc 失败时,那么:
表示RPC的Promise被拒绝,因此调用代码将崩溃,除非它处理这种情况
在主应用程序总线上触发
RPC_ERROR
事件。事件负载包含错误原因的描述:如果是服务器错误(服务器代码抛出异常),则事件负载将是一个具有以下键的对象:
type = 'server'
message(string)
code(number)
name(string)
(optional, used by the error service to look for an appropriate dialog to use when handling the error)subType(string)
(optional, often used to determine the dialog title)data(object)
(optional object that can contain various keys among whichdebug
: the main debug information, with the call stack)
如果是网络错误,则错误描述仅为一个对象
{type: 'network'}
。当发生网络错误时,会显示一个 notification,并定期联系服务器直到它响应。一旦服务器响应,通知将被关闭。
滚动条服务¶
概览¶
技术名称:
scroller
依赖项:无
每当用户在Web客户端中单击锚点时,此服务会自动滚动到目标位置(如果适用)。
该服务添加了一个事件监听器,用于获取文档上的 click
事件。该服务检查其 href 属性中包含的选择器是否有效,以区分锚点和 Odoo 操作(例如 <a href="#target_element"></a>
)。如果不是这种情况,则不执行任何操作。
如果单击似乎针对某个元素,则在主应用程序总线上触发事件 SCROLLER:ANCHOR_LINK_CLICKED
。该事件包含一个自定义事件,其中包含与其 id
相匹配的 element
作为引用。它可能允许其他部分处理与锚点本身相关的行为。原始事件也会被给出,因为它可能需要被阻止。如果未阻止事件,则用户界面将滚动到目标元素。
API¶
下面的值包含在上面解释的 anchor-link-clicked
自定义事件中。
名称 |
类型 |
描述 |
---|---|---|
|
|
被 href 定位的锚点元素 |
|
|
包含在 href 中的 ID |
|
|
原始点击事件 |
注解
The scroller service emits a SCROLLER:ANCHOR_LINK_CLICKED
event on the main bus.
To avoid the default scroll behavior of the scroller service, you must use preventDefault()
on the event given
to the listener so that you can implement your own behavior correctly from the listener.
标题服务¶
概览¶
技术名称:
title
依赖项:无
The title
service offers a simple API that allows to read/modify the document
title. For example, if the current document title is “Odoo”, we can change it
to “Odoo 15 - Apple” by using the following command:
// in some component setup method
const titleService = useService("title");
titleService.setParts({ odoo: "Odoo 15", fruit: "Apple" });
API¶
The title
service manipulates the following interface:
interface Parts {
[key: string]: string | null;
}
每个键表示标题的一部分的标识,每个值是显示的字符串,如果已删除,则为 null
。
它的 API 是:
- current
这是一个表示当前标题的字符串。它的结构如下:
value_1 - ... - value_n
,其中每个value_i
是在getParts
函数返回的Parts
对象中找到的(非空)值。
- getParts()¶
- 返回
分割当前由标题服务维护的
Parts
对象
- setParts(parts)¶
- 参数
parts (
Parts()
) – 表示所需更改的对象
The
setParts
method allows to add/replace/delete several parts of the title. Delete a part (a value) is done by setting the associated key value tonull
.请注意,只能修改单个部分而不影响其他部分。例如,如果标题由以下部分组成:
{ odoo: "Odoo", action: "Import" }
如果
current
的值为Odoo - Import
,那么setParts({ action: null, });
将标题更改为
Odoo
.
用户服务¶
概览¶
技术名称:
user
依赖项:
rpc
The user
service provides a bunch of data and a few helper functions concerning
the connected user.
API¶
名称 |
类型 |
描述 |
---|---|---|
|
|
The 用户上下文 |
|
|
数据库信息 |
|
|
用户主页所使用的操作的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>
is the user in the group
检查用户是否属于某个组
const isInSalesGroup = await userService.hasGroup("sale.group_sales")