Огляд фреймворку¶
Вступ¶
Фреймворк Odoo Javascript - це набір функцій/структурних блоків, що надаються доповненням web/, щоб допомогти створювати застосунки odoo, що працюють у браузері. Водночас, фреймворк Odoo Javascript - це односторінковий застосунок, зазвичай відомий як веб-клієнт (доступний за URL-адресою /web).
Веб-клієнт починався як застосунок, створений з використанням власної системи класів та віджетів, але зараз він переходить на використання нативних класів JavaScript та Owl як системи компонентів. Це пояснює, чому обидві системи зараз використовуються в кодовій базі.
З точки зору загальних принципів, веб-клієнт - це односторінковий застосунок: йому не потрібно запитувати повну сторінку від сервера щоразу, коли користувач виконує дію. Натомість він запитує лише те, що йому потрібно, а потім відповідно замінює/оновлює поточний екран. Крім того, він керує URL-адресою, щоб синхронізувати її з поточним станом.
Фреймворк javascript (повністю або частково) також використовується в інших ситуаціях, таких як вебсайт Odoo або точка продажу. Це посилання здебільшого стосується веб-клієнта.
Примітка
В екосистемі Odoo слова frontend та backend часто використовуються як синоніми веб-сайту odoo (публічного) та веб-клієнта відповідно. Цю термінологію не слід плутати з більш поширеним використанням термінів browser-code (frontend) та server (backend).
Примітка
У цій документації слово компонент завжди стосується нових компонентів Owl, а віджет - старих віджетів Odoo.
Примітка
Всю нову розробку, якщо це можливо, слід виконувати в Owl!
Структура коду¶
Папка web/static/src містить всю кодову базу javascript (а також css та шаблони) web/. Ось список найважливіших папок:
core/більшість низькорівневих функційfields/усі компоненти поляviews/усі компоненти представлень JavaScript (form,list, …)панель керування
search/, рядок пошуку, панель пошуку, …webclient/код, специфічний для веб-клієнта: панель навігації, меню користувача, служба дій тощо, …
web/static/src - це коренева папка. Все, що знаходиться всередині, можна просто імпортувати, використовуючи префікс @web. Наприклад, ось як можна імпортувати функцію memoize, розташовану в web/static/src/core/utils/functions:
import { memoize } from "@web/core/utils/functions";
Архітектура WebClient¶
Як згадувалося вище, веб-клієнт - це застосунок у стилі owl. Ось дещо спрощена версія його шаблону:
<t t-name="web.WebClient">
<body class="o_web_client">
<NavBar/>
<ActionContainer/>
<MainComponentsContainer/>
</body>
</t>
Як ми бачимо, це, по суті, обгортка для панелі навігації, поточної дії та деяких додаткових компонентів. ActionContainer - це компонент вищого порядку, який відображатиме поточний контролер дій (тобто дію клієнта або певний вигляд у випадку дій типу act_window). Керування діями є величезною частиною його роботи: служба дій зберігає в пам’яті стек усіх активних дій (представлених у навігаційних сухарях) та координує кожну зміну.
Ще одна цікава річ, яку варто відзначити, це MainComponentsContainer: це просто компонент, який відображає всі компоненти, зареєстровані в реєстрі main_components. Таким чином інші частини системи можуть розширювати веб-клієнт.
Середовище¶
Як застосунок Owl, веб-клієнт Odoo визначає власне середовище (компоненти можуть отримати до нього доступ за допомогою this.env). Ось опис того, що Odoo додає до спільного об’єкта env:
Key |
Значення |
|---|---|
|
вимагається Owl (містить усі шаблони) |
|
main bus, використовується для координації деяких загальних подій |
|
усі розгорнуті services (зазвичай слід отримувати доступ за допомогою гачка |
|
рядок. Якщо не порожній, веб-клієнт знаходиться в режимі налагодження |
|
функція перекладу |
|
логічне значення. Якщо значення true, веб-клієнт наразі перебуває в мобільному режимі (ширина екрана <= 767px) |
Отже, наприклад, щоб перекласти рядок у компоненті (примітка: шаблони перекладаються автоматично, тому в цьому випадку не потрібно виконувати жодних конкретних дій), можна зробити ось що:
const someString = this.env._t('some text');
Примітка
Наявність посилання на середовище є досить потужною, оскільки забезпечує доступ до всіх сервісів. Це корисно в багатьох випадках: наприклад, елементи меню користувача здебільшого визначаються як рядок, а функція приймає env як унікальний аргумент. Цього достатньо, щоб виразити всі потреби меню користувача.
Будівельні блоки¶
Більшість веб-клієнтів побудовано з кількома типами абстракцій: реєстри, сервіси, компоненти та перехоплювачі.
Реєстри¶
Реєстри - це, по суті, просте зіставлення ключів/значень, яке зберігає певний тип об’єктів. Вони є важливою частиною розширюваності інтерфейсу користувача: після реєстрації певного об’єкта решта веб-клієнта може його використовувати. Наприклад, реєстр полів містить усі компоненти полів (або віджети), які можна використовувати у представленнях.
import { registry } from "./core/registry";
class MyFieldChar extends owl.Component {
// some code
}
registry.category("fields").add("my_field_char", MyFieldChar);
Зверніть увагу, що ми імпортуємо основний реєстр з @web/core/registry, а потім відкриваємо fields підреєстру.
Послуги¶
Сервіси - це довговічні фрагменти коду, які надають певну функцію. Вони можуть бути імпортовані компонентами (за допомогою useService) або іншими сервісами. Також вони можуть оголошувати набір залежностей. У цьому сенсі сервіси є, по суті, системою DI (впровадження залежностей). Наприклад, сервіс notification надає спосіб відображення сповіщення, або сервіс rpc - це правильний спосіб виконання запиту до сервера Odoo.
У наступному прикладі реєструється простий сервіс, який відображає сповіщення кожні 5 секунд:
import { registry } from "./core/registry";
const serviceRegistry = registry.category("services");
const myService = {
dependencies: ["notification"],
start(env, { notification }) {
let counter = 1;
setInterval(() => {
notification.add(`Tick Tock ${counter++}`);
}, 5000);
}
};
serviceRegistry.add("myService", myService);
Компоненти та Hooks¶
Компоненти та hooks – це ідеї, що походять із системи компонентів Owl. Компоненти Odoo – це просто компоненти owl, які є частиною веб-клієнта.
Hooks – це спосіб факторизації коду, навіть якщо це залежить від життєвого циклу. Це компонований/функціональний спосіб впровадження функції в компонент. Їх можна розглядати як різновид міксину.
function useCurrentTime() {
const state = useState({ now: new Date() });
const update = () => state.now = new Date();
let timer;
onWillStart(() => timer = setInterval(update, 1000));
onWillUnmount(() => clearInterval(timer));
return state;
}
Контекст¶
Важливою концепцією в javascript Odoo є контекст: він надає коду можливість надати більше контексту виклику функції або rpc, щоб інші частини системи могли належним чином реагувати на цю інформацію. У певному сенсі це схоже на мішок інформації, який поширюється всюди. Це корисно в деяких ситуаціях, наприклад, для повідомлення серверу Odoo про те, що модель rpc походить з певного вигляду форми, або для активації/вимикання деяких функцій у компоненті.
У веб-клієнті Odoo є два різних контексти: контекст користувача та контекст дії (тому слід бути обережними, використовуючи слово контекст: воно може означати різне залежно від ситуації).
Примітка
Об’єкт context може бути корисним у багатьох випадках, але слід бути обережним і не зловживати ним! Багато проблем можна вирішити стандартним способом без зміни контексту.
Контекст користувача¶
Контекст користувача - це невеликий об’єкт, що містить різноманітну інформацію, пов’язану з поточним користувачем. Він доступний через сервіс user:
class MyComponent extends Component {
setup() {
const user = useService("user");
console.log(user.context);
}
}
Він містить таку інформацію:
Name |
Тип |
Опис |
|---|---|---|
|
|
список активних ідентифікаторів компаній для користувача |
|
|
код мови користувача (наприклад, «en_us») |
|
|
поточний часовий пояс користувача (наприклад, «Europe/Brussels») |
На практиці, сервіс orm автоматично додає контекст користувача до кожного свого запиту. Саме тому в більшості випадків його безпосередньо імпортувати не потрібно.
Примітка
Перший елемент allowed_company_ids – це основна компанія користувача.
Контекст дії¶
ir.actions.act_window та ir.actions.client підтримують необов’язкове поле context. Це поле є char, що представляє об’єкт. Щоразу, коли відповідна дія завантажується у веб-клієнт, це поле контексту буде оцінено як об’єкт і передано компоненту, що відповідає дії.
<field name="context">{'search_default_customer': 1}</field>
Його можна використовувати багатьма різними способами. Наприклад, представлення додають контекст дії до кожного запиту, що надсилається до сервера. Ще одне важливе використання - активувати певний фільтр пошуку за замовчуванням (див. приклад вище).
Іноді, коли ми виконуємо нові дії вручну (тобто програмно, у javascript), корисно мати можливість розширити контекст дії. Це можна зробити за допомогою аргументу additional_context.
// in setup
let actionService = useService("action");
// in some event handler
actionService.doAction("addon_name.something", {
additional_context:{
default_period_id: defaultPeriodId
}
});
У цьому прикладі буде завантажено дію з xml_id addon_name.something, а її контекст буде розширено значенням default_period_id. Це дуже важливий варіант використання, який дозволяє розробникам поєднувати дії разом, надаючи певну інформацію наступній дії.
Інтерпретатор Python¶
Фреймворк Odoo має вбудований невеликий інтерпретатор Python. Його призначення - обчислювати невеликі вирази Python. Це важливо, оскільки представлення в Odoo мають модифікатори, написані на Python, але їх має обчислювати браузер.
Приклад:
import { evaluateExpr } from "@web/core/py_js/py";
evaluateExpr("1 + 2*{'a': 1}.get('b', 54) + v", { v: 33 }); // returns 142
JavaScript-код py експортує 5 функцій:
- tokenize(expr)¶
- Аргументи
expr (
string()) – вираз для токенізації
- Повертає
Token[] список токенів
- parse(tokens)¶
- Аргументи
tokens (
Token[]()) – список токенів
- Повертає
AST - абстрактна структура синтаксичного дерева, що представляє вираз
- parseExpr(expr)¶
- Аргументи
expr (
string()) – рядок, що представляє дійсний вираз Python
- Повертає
AST - абстрактна структура синтаксичного дерева, що представляє вираз
- evaluate(ast[, context])¶
- Аргументи
ast (
AST()) – структура AST, що представляє виразcontext (
Object()) – об’єкт, що забезпечує додатковий контекст оцінювання
- Повертає
будь-яке результуюче значення виразу, з урахуванням контексту
- evaluateExpr(expr[, context])¶
- Аргументи
expr (
string()) – рядок, що представляє дійсний вираз Pythoncontext (
Object()) – об’єкт, що забезпечує додатковий контекст оцінювання
- Повертає
будь-яке результуюче значення виразу, з урахуванням контексту
Домени¶
Загалом кажучи, домени в Odoo представляють собою набір записів, що відповідають деяким заданим умовам. У javascript вони зазвичай представлені або як список умов (або операторів: |, & або ! у префіксній нотації), або як рядкові вирази. Вони не обов’язково повинні бути нормалізованими (оператор & мається на увазі, якщо необхідно). Наприклад:
// list of conditions
[]
[["a", "=", 3]]
[["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]
["&", "&", ["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]
["&", "!", ["a", "=", 1], "|", ["a", "=", 2], ["a", "=", 3]]
// string expressions
"[('some_file', '>', a)]"
"[('date','>=', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"
"[('date', '!=', False)]"
Рядкові вирази потужніші за спискові: вони можуть містити вирази Python та неоцінені значення, що залежить від певного контексту оцінювання. Однак маніпулювання рядковими виразами є складнішим.
Оскільки домени є досить важливими у веб-клієнті, Odoo надає клас Domain:
new Domain([["a", "=", 3]]).contains({ a: 3 }) // true
const domain = new Domain(["&", "&", ["a", "=", 1], ["b", "=", 2], ["c", "=", 3]]);
domain.contains({ a: 1, b: 2, c: 3 }); // true
domain.contains({ a: -1, b: 2, c: 3 }); // false
// next expression returns ["|", ("a", "=", 1), ("b", "<=", 3)]
Domain.or([[["a", "=", 1]], "[('b', '<=', 3)]"]).toString();
Ось опис класу Domain:
- class Domain([descr])¶
- Аргументи
descr (
string | any[] | Domain()) – опис домену
- Domain.contains(record)¶
- Аргументи
record (
Object()) – об’єкт запису
- Повертає
boolean
Повертає значення true, якщо запис відповідає всім умовам, визначеним доменом
- Domain.toString()¶
- Повертає
string
Повертає рядковий опис домену
- Domain.toList([context])¶
- Аргументи
context (
Object()) – контекст оцінювання
- Повертає
any[]
Повертає опис домену у вигляді списку. Зверніть увагу, що цей метод приймає необов’язковий об’єкт
context, який буде використано для заміни всіх вільних змінних.new Domain(`[('a', '>', b)]`).toList({ b:3 }); // [['a', '>', 3]]
Клас Domain також надає 4 корисні статичні методи для об’єднання доменів:
// ["&", ("a", "=", 1), ("uid", "<=", uid)]
Domain.and([[["a", "=", 1]], "[('uid', '<=', uid)]"]).toString();
// ["|", ("a", "=", 1), ("uid", "<=", uid)]
Domain.or([[["a", "=", 1]], "[('uid', '<=', uid)]"]).toString();
// ["!", ("a", "=", 1)]
Domain.not([["a", "=", 1]]).toString();
// ["&", ("a", "=", 1), ("uid", "<=", uid)]
Domain.combine([[["a", "=", 1]], "[('uid', '<=', uid)]"], "AND").toString();
- static Domain.and(domains)¶
- Параметри
domains (string[] | any[][] | Domain[]) – список представлень доменів
- Повертає
Домен
Повертає домен, що представляє собою перетин усіх доменів.
- static Domain.or(domains)¶
- Параметри
domains (string[] | any[][] | Domain[]) – список представлень доменів
- Повертає
Домен
Повертає домен, що представляє об’єднання всіх доменів.
- static Domain.not(domain)¶
- Параметри
domain (string | any[] | Domain) – представлення домену
- Повертає
Домен
Повертає домен, що представляє заперечення аргумента домену
- static Domain.combine(domains, operator)¶
- Параметри
domains (string[] | any[][] | Domain[]) – список представлень доменів
operator ('AND' or 'OR') – оператор
- Повертає
Домен
Повертає домен, що представляє або перетин, або об’єднання всіх доменів, залежно від значення аргумента оператора.
Bus¶
Об’єкт веб-клієнта environment містить шину подій під назвою bus. Її призначення - дозволити різним частинам системи належним чином координувати свою роботу, не пов’язуючи їх між собою. env.bus - це шина подій EventBus, яку слід використовувати для глобальних подій, що цікавлять.
// for example, in some service code:
env.bus.on("WEB_CLIENT_READY", null, doSomething);
Ось список подій, які можуть бути ініційовані на цій шині:
Повідомлення |
Payload |
Тригер |
|---|---|---|
|
режим, що вказує, яка частина інтерфейсу користувача була оновлена („current“, „new“ або „fullscreen“) |
виконання запитуваної дії для менеджера дій |
|
наступна інформація про рендеринг |
менеджер дій завершив обчислення наступного інтерфейсу |
|
none |
поточний додаток сервісу меню змінився |
|
none |
хеш URL-адреси було змінено |
|
rpc id |
щойно розпочато виконання RPC-запиту |
|
rpc id |
запит RPC виконано |
|
none |
веб-клієнт встановлено |
|
none |
основне представлення має бути зосереджене на собі |
|
none |
всі внутрішні кеші слід очистити |
|
список функцій |
усі представлення з незбереженими змінами слід очистити та надіслати зворотний виклик у список |
Browser Object¶
Фреймворк javascript також надає спеціальний об’єкт browser, який забезпечує доступ до багатьох API браузера, таких як location, localStorage або setTimeout. Наприклад, ось як можна використовувати функцію browser.setTimeout:
import { browser } from "@web/core/browser/browser";
// somewhere in code
browser.setTimeout(someFunction, 1000);
Це здебільшого цікаво для цілей тестування: весь код, що використовує об’єкт браузера, можна легко протестувати, використовуючи імітацію відповідних функцій протягом усього тесту.
Він містить наступний вміст:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Режим налагодження¶
Odoo іноді може працювати в спеціальному режимі, який називається режимом debug. Він використовується для двох основних цілей:
відображати додаткову інформацію/поля для деяких конкретних екранів,
надати деякі додаткові інструменти, які допоможуть розробникам налагоджувати інтерфейс Odoo.
Режим debug описується рядком. Порожній рядок означає, що режим debug неактивний. В іншому випадку він активний. Якщо рядок містить assets або tests, то активуються відповідні підрежими (див. нижче). Обидва режими можуть бути активними одночасно, наприклад, з рядком assets,tests.
Поточне значення режиму debug можна прочитати у environment: env.debug.
Порада
Щоб відображати меню, поля або елементи перегляду лише в режимі налагодження, слід обрати групу base.group_no_one:
<field name="fname" groups="base.group_no_one"/>
Перегляньте також
Режим активів¶
Підрежим debug=assets корисний для налагодження коду javascript: після активації пакети assets більше не мініфікуються, а також генеруються карти вихідного коду. Це робить його корисним для налагодження всіх видів коду javascript.
Режим тестів¶
Існує ще один підрежим під назвою tests: якщо його ввімкнено, сервер вставляє пакет web.assets_tests на сторінку. Цей пакет містить здебільшого тестові тури (тури, метою яких є тестування функції, а не показ чогось цікавого користувачам). Режим tests корисний для запуску цих турів.
Перегляньте також