服务

服务是提供功能的长期存在的代码片段。它们可以被组件(使用 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(...);
    });
  }
}

参考列表

技术名称

简短描述

cookie

读取或修改 cookies

effect

显示图形效果

http

执行低级别的http调用

notification

显示通知

router

管理浏览器URL

rpc

向服务器发送请求

scroller

处理锚元素上的点击事件

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的名称

删除 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" });

名称

类型

描述

params.Component

owl.Component?

在 RainbowMan 内实例化的组件类(将替换消息)。

params.props

object?={}

如果给定了params.Component,则可以使用此参数传递其props。

params.message

string?="Well Done!"

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

如果用户禁用了效果,则彩虹人将不会出现,而是显示一个简单的通知作为备选方案。

如果启用了效果并且给出了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" will make rainbowman dissapear quickly.

"medium""slow" 会在消失之前等待更长的时间(当 params.message 较长时可用)。

"no" will keep rainbowman on screen until user clicks anywhere outside rainbowman.

如何添加特效

效果存储在名为 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" });
Odoo在棕褐色中

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.

该服务提供了一种发送 getpost 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 given

  • hash (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()) – 一个有效的URL

  • wait (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 a XMLHTTPRequest object. In that case, the rpc method will simply use it instead of creating a new one. This is useful when one accesses advanced features of the XMLHTTPRequest 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 which debug : 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 自定义事件中。

名称

类型

描述

element

HTMLElement | null

被 href 定位的锚点元素

id

string

包含在 href 中的 ID

originalEv

Event

原始点击事件

注解

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 to null.

请注意,只能修改单个部分而不影响其他部分。例如,如果标题由以下部分组成:

{ 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

名称

类型

描述

context

Object

The 用户上下文

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> is the user in the group

检查用户是否属于某个组

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