Послуги

Сервіси – це довговічні фрагменти коду, які надають певну функцію. Вони можуть бути імпортовані компонентами (за допомогою 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);

Під час запуску веб-клієнт запускає всі служби, присутні в реєстрі services. Зверніть увагу, що назва, яка використовується в реєстрі, є назвою служби.

Примітка

Більшість коду, який не є компонентом, має бути упакований в сервіс, особливо якщо він виконує якийсь побічний ефект. Це дуже корисно для цілей тестування: тести можуть вибирати, які сервіси активні, тому менше шансів, що небажані побічні ефекти втручатимуться в тестований код.

Визначення послуги

Сервіс повинен реалізувати наступний інтерфейс:

dependencies

Необов’язковий список рядків. Це список усіх залежностей (інших сервісів), які потрібні цьому сервісу.

start(env, deps)
Аргументи
  • env (Environment()) – середовище застосування

  • deps (Object()) – усі запитувані залежності

Повертає

значення послуги або Promise<value of service>

Це основне визначення сервісу. Він може повертати або значення, або обіцянку. У такому випадку завантажувач сервісу просто чекає, поки обіцянка перетвориться на значення, яке потім є значенням сервісу.

Деякі сервіси не експортують жодних значень. Вони можуть просто виконувати свою роботу без потреби безпосереднього виклику іншим кодом. У такому випадку їхнє значення буде встановлено на null у env.services.

async

Необов’язкове значення. Якщо вказано, воно має бути true або списком рядків.

Деяким сервісам потрібно надавати асинхронний API. Наприклад, сервіс rpc є асинхронною функцією, або сервіс orm надає набір функцій для виклику сервера Odoo.

У такому випадку компоненти, що використовують сервіс, можуть бути знищені до завершення асинхронного виклику функції. У більшості випадків асинхронний виклик функції потрібно ігнорувати. В іншому випадку це потенційно дуже ризиковано, оскільки базовий компонент більше не активний. Прапорець async - це спосіб зробити саме це: він сигналізує творцю сервісу, що всі асинхронні виклики, що надходять від компонентів, слід залишити в очікуванні, якщо компонент знищено.

Використання сервісу

Сервіс, який залежить від інших сервісів і правильно оголосив свої dependencies, просто отримує посилання на відповідні сервіси у другому аргументі методу start.

Hook useService - це правильний спосіб використання сервісу в компоненті. Він просто повертає посилання на значення сервісу, яке потім може бути використане компонентом. Наприклад:

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

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

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

Список посилань

Технічна назва

Короткий опис

cookie

читати або змінювати файли cookie

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.",
});

Попередження

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

Name

Тип

Опис

params.Component

owl.Component?

Клас компонента для створення екземпляра всередині RainbowMan (замінить повідомлення).

params.props

object?={}

Якщо задано params.Component, його властивості можна передати з цим аргументом.

params.message

string?="Well Done!"

Повідомлення – це повідомлення, яке тримає в руках райдужна людина.

Якщо ефекти для користувача вимкнено, райдужна людина не з’явиться, а як резервний варіант відобразиться просте сповіщення.

Якщо ефекти ввімкнено та задано params.Component, params.message не використовується.

Повідомлення - це простий рядок або рядок, що представляє html (бажано використовувати params.Component, якщо вам потрібна взаємодія в DOM).

params.messageIsHtml

boolean?=false

Встановіть значення true, якщо повідомлення являє собою html, тобто воно буде коректно вставлено в DOM.

params.img_url

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

URL-адреса зображення, яке потрібно відобразити всередині веселки.

params.fadeout

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

Затримка зникнення райдужної людини.

"fast" змусить райдужну людину швидко зникнути.

"medium" та "slow" чекатимуть трохи довше, перш ніж зникнути (можна використовувати, коли params.message довший).

"no" залишить rainbowman на екрані, доки користувач не клацне будь-де за межами rainbowman.

Як додати ефект

Ефекти зберігаються в реєстрі під назвою effects. Ви можете додавати нові ефекти, вказавши назву та функцію.

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

Функція повинна відповідати цьому API:

<newEffectFunction>(env, params)
Аргументи
  • env (Env()) – середовище, отримане сервісом

  • params (object()) – параметри, отримані від функції додавання на сервісі.

Повертає

({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" });
Оду в сепії

Http-сервіс

Огляд

  • Технічна назва: http

  • Залежності: Немає

Хоча більшість взаємодій між клієнтом і сервером в odoo є RPC (XMLHTTPRequest), іноді може знадобитися контроль нижчого рівня над запитами.

Цей сервіс надає спосіб надсилання http-запитів get та post <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.

Надсилає запит на отримання.

async post(route[, params = {}, readMethod = "json"])
Аргументи
  • route (string()) – URL-адреса для надсилання запиту

  • params (object()) – дані ключа-значення, які потрібно встановити в частині даних форми запиту

  • readMethod (string()) – тип вмісту відповіді. Може бути «text», «json», «formData», «blob», «arrayBuffer».

Повертає

результат запиту у форматі, визначеному аргументом readMethod.

Надсилає запит на публікацію.

Приклад

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 дозволяє відображати сповіщення на екрані.

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

API

add(message[, options])
Аргументи
  • message (string()) – повідомлення сповіщення, яке має відображатися

  • options (object()) – опції сповіщення

Повертає

функція для закриття сповіщення

Показує сповіщення.

Опції визначаються:

Name

Тип

Опис

title

string

Додатии заголовок до сповіщення

type

warning | danger | success | info

Змінює колір фону відповідно до типу

sticky

boolean

Чи має сповіщення залишатися активним до його відхилення

className

string

додатковий клас CSS, який буде додано до сповіщення

onClose

function

зворотний виклик, який буде виконано після закриття сповіщення

buttons

button[] (див. нижче)

список кнопок для відображення в сповіщенні

Кнопки визначаються:

Name

Тип

Опис

name

string

Текст кнопки

onClick

function

зворотний виклик для виконання при натисканні кнопки

primary

boolean

чи слід стилізувати кнопку як основну кнопку

Приклади

Сповіщення про укладення угоди купівлі-продажу з кнопкою переходу на певну сторінку комісійних.

// 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 Service

Огляд

  • Технічна назва: router

  • Залежності: немає

Служба router надає три функції:

  • інформація про поточний маршрут

  • спосіб оновлення URL-адреси застосунком залежно від її стану

  • прослуховує кожну зміну хешу та повідомляє решту додатку

API

current

До поточного маршруту можна отримати доступ за допомогою ключа current. Це об’єкт з такою інформацією:

  • pathname (string): шлях до поточного розташування (найімовірніше /web)

  • search (object): словник, що відображає кожне ключове слово пошуку (рядок запиту) з URL-адреси на його значення. Пустий рядок є значенням, якщо значення не було вказано явно.

  • хеш (об'єкт): те саме, що й вище, але для значень, описаних у хеші.

Наприклад:

// 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.

Оновлює URL-адресу кожною парою ключ/значення з об’єкта hash. Якщо значення встановлено як порожній рядок, ключ додається до URL-адреси без відповідного значення.

Якщо значення true, аргумент replace повідомляє маршрутизатору, що хеш URL-адреси слід повністю замінити (тому значення, яких немає в об’єкті hash, будуть видалені).

Цей виклик методу не перезавантажує сторінку. Він також не ініціює подію hashchange, а також ROUTE_CHANGE в main bus. Це тому, що цей метод призначений лише для оновлення 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()) – якщо значення true, чекати готовності сервера та перенаправляти після цього

Перенаправити браузер на url. Цей метод перезавантажує сторінку. Аргумент wait використовується рідко: він корисний у деяких випадках, коли ми знаємо, що сервер буде недоступний протягом короткого періоду, зазвичай одразу після оновлення або встановлення доповнення.

Примітка

Служба router генерує подію ROUTE_CHANGE на main bus щоразу, коли поточний маршрут змінюється.

Служба 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()) – (необов’язково) параметри, що надсилаються на сервер

  • settings (Object()) – (необов’язково) налаштування запиту (див. нижче)

Об’єкт settings може містити:

  • xhr, який має бути об’єктом XMLHTTPRequest. У такому випадку метод rpc просто використовуватиме його замість створення нового. Це корисно, коли хтось отримує доступ до розширених функцій API XMLHTTPRequest.

  • silent (boolean) Якщо встановлено значення true, веб-клієнт не надаватиме зворотний зв’язок про наявність очікуваного rpc.

Служба rpc взаємодіє з сервером за допомогою об’єкта XMLHTTPRequest, налаштованого для роботи з типом вмісту application/json. Отже, очевидно, що вміст запиту має бути серіалізованим у форматі JSON. Кожен запит, виконаний цією службою, використовує метод http POST.

Помилки сервера насправді повертають відповідь з http-кодом 200. Але служба rpc трактуватиме їх як помилку.

Обробка помилок

RPC може вийти з ладу з двох основних причин:

  • або сервер odoo повертає помилку (тому ми називаємо це помилкою сервера). У такому випадку http-запит поверне код http 200, АЛЕ з об’єктом відповіді, що містить ключ error.

  • або є якась інша помилка мережі

Коли RPC не працює, тоді:

  • promise, що представляє rpc, відхилено, тому викликаючий код аварійно завершить роботу, якщо він не обробить ситуацію.

  • на головній шині програми спрацьовує подія RPC_ERROR. Корисне навантаження події містить опис причини помилки:

    Якщо це помилка сервера (код сервера видав виняток), у такому випадку корисним навантаженням події буде об’єкт з такими ключами:

    • type = 'server'

    • message(string)

    • code(number)

    • name(string) (необов’язково, використовується службою помилок для пошуку відповідного діалогового вікна для використання під час обробки помилки)

    • subType(string) (необов’язково, часто використовується для визначення заголовка діалогового вікна)

    • data(object) (необов’язковий об’єкт, який може містити різні ключі, серед яких debug: основна налагоджувальна інформація зі стеком викликів)

    Якщо це мережева помилка, то опис помилки – це просто об’єкт {type: 'network'}. Коли виникає мережева помилка, відображається notification, і сервер регулярно зв’язується з ним, доки він не відповість. Сповіщення закривається, щойно сервер відповідає.

Scroller service

Огляд

  • Технічна назва: scroller

  • Залежності: немає

Щоразу, коли користувач натискає на якір у веб-клієнті, цей сервіс автоматично прокручує до цілі (якщо це доречно).

Сервіс додає слухач подій для отримання click на документі. Сервіс перевіряє, чи селектор, що міститься в його атрибуті href, є дійсним для розрізнення якорів та дій Odoo (наприклад, <a href="#target_element"></a>). Він нічого не робить, якщо це не так.

Подія SCROLLER:ANCHOR_LINK_CLICKED спрацьовує на головній шині застосунку, якщо клік, здається, спрямований на елемент. Подія містить користувацьку подію, що містить збіг element та його id як посилання. Це може дозволити іншим частинам обробляти поведінку відносно самих якорів. Початкова подія також вказана, оскільки її, можливо, потрібно запобігти. Якщо подію не запобігти, тоді інтерфейс користувача прокрутиться до цільового елемента.

API

Наступні значення містяться у користувацькій події anchor-link-clicked, поясненій вище.

Name

Тип

Опис

element

HTMLElement | null

Якірний елемент, на який орієнтований href

id

string

id, що міститься в href

originalEv

Event

Початкова подія кліку

Примітка

Служба scroller генерує подію SCROLLER:ANCHOR_LINK_CLICKED на main bus. Щоб уникнути поведінки скролера за замовчуванням, необхідно використовувати 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 — це (не null) значення, знайдене в об’єкті Parts (повернене функцією getParts)

getParts()
Повертає

Частини поточного об’єкта Parts, що підтримується службою заголовків

setParts(parts)
Аргументи
  • parts (Parts()) – об’єкт, що представляє необхідну зміну

Метод setParts дозволяє додавати/замінювати/видаляти кілька частин заголовка. Видалення частини (значення) здійснюється шляхом встановлення відповідного значення ключа в null.

Зверніть увагу, що можна змінити лише одну частину, не впливаючи на інші частини. Наприклад, якщо заголовок складається з таких частин:

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

де значення current дорівнює Odoo - Import, тоді

setParts({
  action: null,
});

змінить назву на Odoo.

Сервіс користувача

Огляд

  • Технічна назва: user

  • Залежності: rpc

Сервіс user надає купу даних та кілька допоміжних функцій щодо підключеного користувача.

API

Name

Тип

Опис

context

Object

user context

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()) – об’єкт, за допомогою якого потрібно оновити контекст

оновити user context заданим об’єктом.

userService.updateContext({ isFriend: true })
removeFromContext(key)
Аргументи
  • key (string()) – ключ цільового атрибута

видалити значення з заданим ключем з user context

userService.removeFromContext("isFriend")
hasGroup(group)
Аргументи
  • group (string()) – xml_id групи, яку потрібно шукати

Повертає

Promise<boolean> – це користувач у групі

перевірити, чи є користувач частиною групи

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