Розділ 2: Створення інф. панелі

У першій частині цього посібника ви ознайомилися з більшістю ідей Owl. Тепер настав час повністю ознайомитися з фреймворком Odoo JavaScript, який використовується веб-клієнтом.

../../../_images/previously_learned.svg

Для початку вам знадобиться запущений сервер Odoo та налаштоване середовище розробки. Перш ніж розпочати вправи, переконайтеся, що ви виконали всі кроки, описані в цьому вступ до посібника. У цьому розділі ми почнемо з порожньої панелі інструментів, що надається доповненням awesome_dashboard. Ми поступово додаватимемо до неї функції, використовуючи фреймворк Odoo JavaScript.

Ціль

../../../_images/overview_02.png

Рішення для кожної вправи цього розділу розміщені в офіційний репозиторій навчальних посібників Odoo.

1. Новий макет

Більшість екранів у веб-клієнті Odoo використовують загальний макет: панель керування зверху з кількома кнопками та основна зона вмісту трохи нижче. Це робиться за допомогою компонента Макет, доступного в @web/search/layout.

  1. Оновіть компонент AwesomeDashboard, розташований у awesome_dashboard/static/src/, щоб використовувати компонент Layout. Ви можете використовувати {controlPanel: {} } для властивостей display компонента Layout.

  2. Додати властивість className до Layout: className="'o_dashboard h-100'"

  3. Додайте файл dashboard.scss, у якому встановіть колір фону .o_dashboard на сірий (або ваш улюблений колір)

Відкрийте http://localhost:8069/web, потім запустіть програму Awesome Dashboard та перегляньте результат.

../../../_images/new_layout.png

Теорія: Сервіси

На практиці кожен компонент (крім кореневого компонента) може бути знищений у будь-який час та замінений (або ні) іншим компонентом. Це означає, що внутрішній стан кожного компонента не є постійним. У багатьох випадках це нормально, але, безумовно, є ситуації, коли ми хочемо зберегти деякі дані. Наприклад, усі повідомлення Обговорення не повинні перезавантажуватися щоразу, коли ми відображаємо канал.

Також може статися так, що нам потрібно написати код, який не є компонентом. Можливо, щось, що обробляє всі штрих-коди або керує конфігурацією користувача (контекстом тощо).

Фреймворк 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);

До сервісів може отримати доступ будь-який компонент. Уявіть, що у нас є сервіс для підтримки деякого спільного стану:

import { registry } from "@web/core/registry";

const sharedStateService = {
    start(env) {
        let state = {};
        return {
            getValue(key) {
                return state[key];
            },
            setValue(key, value) {
                state[key] = value;
            },
        };
    },
};

registry.category("services").add("shared_state", sharedStateService);

Тоді будь-який компонент може зробити це:

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

setup() {
   this.sharedState = useService("shared_state");
   const value = this.sharedState.getValue("somekey");
   // do something with value
}

2. Додайте кілька кнопок для швидкої навігації

Одним із важливих сервісів, що надаються Odoo, є сервіс action: він може виконувати всі види стандартних дій, визначених Odoo. Наприклад, ось як один компонент може виконати дію за своїм xml id:

import { useService } from "@web/core/utils/hooks";
...
setup() {
      this.action = useService("action");
}
openSettings() {
      this.action.doAction("base_setup.action_general_configuration");
}
...

Тепер додамо дві кнопки до нашої панелі керування:

  1. Кнопка Клієнти, яка відкриває канбан-представлення з усіма клієнтами (ця дія вже існує, тому слід використовувати її xml id).

  2. Кнопка Leads, яка відкриває динамічну дію на моделі crm.lead зі списком та формою перегляду. Скористайтеся прикладом це використання сервісу дій.

../../../_images/navigation_buttons.png

Перегляньте також

Код: служба дій

3. Додайте елемент інф. панелі

Давайте тепер покращимо наш контент.

  1. Створіть універсальний компонент DashboardItem, який відображає свій слот за замовчуванням у зручному макеті картки. Він повинен приймати необов’язковий номер властивості size, який за замовчуванням дорівнює 1. Ширина має бути жорстко закодована як (18*size)rem.

  2. Додайте дві картки на інформаційну панель. Одну без розміру, а іншу – з розміром 2.

../../../_images/dashboard_item.png

Перегляньте також

Owl’s slot system

4. Виклик до серверу, додайте деяку статистику

Давайте покращимо інф. панель, додавши кілька елементів для відображення реальних бізнес-даних. Доповнення awesome_dashboard надає маршрут /awesome_dashboard/statistics, який призначений для повернення цікавої інформації.

Щоб викликати певний контролер, нам потрібно використовувати rpc-сервіс. Він експортує лише одну функцію, яка виконує запит: rpc(route, params, settings). Простий запит може виглядати так:

setup() {
      this.rpc = useService("rpc");
      onWillStart(async () => {
         const result = await this.rpc("/my/controller", {a: 1, b: 2});
         // ...
      });
}
  1. Оновіть Dashboard, щоб він використовував службу rpc.

  2. Викличте маршрут статистики /awesome_dashboard/statistics у перехоплювачі onWillStart.

  3. Відобразити на інф. панелі кілька карток, що містять:

    • Кількість нових замовлень цього місяця

    • Загальна кількість нових замовлень цього місяця

    • Середня кількість футболок на замовлення цього місяця

    • Кількість скасованих замовлень цього місяця

    • Середній час, необхідний для переходу замовлення від стану „new“ до „sent“ або „cancelled“

../../../_images/statistics1.png

Перегляньте також

Код: rpc-сервіс

5. Кешування мережевих викликів, створення служби

Якщо ви відкриєте вкладку Network у інструментах розробника вашого браузера, то побачите, що виклик /awesome_dashboard/statistics виконується щоразу, коли відображається дія клієнта. Це тому, що хук onWillStart викликається щоразу, коли монтується компонент Dashboard. Але в цьому випадку ми б воліли робити це лише вперше, тому нам насправді потрібно підтримувати певний стан поза компонентом Dashboard. Це гарний варіант використання для сервісу!

  1. Зареєструйте та імпортуйте новий сервіс awesome_dashboard.statistics.

  2. Він повинен забезпечити функцію loadStatistics, яка після виклику виконує фактичний rpc та завжди повертає ту саму інформацію.

  3. Використовуйте допоміжну функцію запам’ятовувати з @web/core/utils/functions, яка дозволяє кешувати статистику.

  4. Використовуйте цей сервіс у компоненті Dashboard.

  5. Перевірте, чи працює він належним чином.

6. Відображення кругової діаграми

Усім подобаються діаграми (!), тож давайте додамо кругову діаграму на нашу панель інструментів. Вона відображатиме пропорції проданих футболок для кожного розміру: S/M/L/XL/XXL.

Для цієї вправи ми використовуватимемо Chart.js. Це бібліотека діаграм, яка використовується графічним представленням. Однак вона не завантажується за замовчуванням, тому нам потрібно буде або додати її до нашого пакета ресурсів, або відкладено завантажити її. Відкладене завантаження зазвичай краще, оскільки нашим користувачам не доведеться щоразу завантажувати код chartjs, якщо він їм не потрібен.

  1. Створіть компонент PieChart.

  2. У методі onWillStart для завантаження chartjs можна використовувати функцію loadJs для завантаження /web/static/lib/Chart/Chart.js.

  3. Використайте компонент PieChart в DashboardItem для відображення кругова діаграма, яка показує кількість кожної проданої футболки кожного розміру (ця інформація доступна в маршруті /statistics). Зверніть увагу, що ви можете використовувати властивість size, щоб зробити її більшою.

  4. Компонент PieChart повинен буде відобразити полотно та малювати на ньому за допомогою chart.js.

  5. Зробіть так, щоб це спрацювало!

../../../_images/pie_chart.png

7. Оновлення в реальному житті

Оскільки ми перемістили завантаження даних у кеш, вони ніколи не оновлюються. Але припустимо, що ми розглядаємо дані, що швидко змінюються, тому ми хочемо періодично (наприклад, кожні 10 хвилин) перезавантажувати свіжі дані.

Це досить просто реалізувати за допомогою setTimeout або setInterval у сервісі статистики. Однак, ось у чому складність: якщо інф. панель відображається на даний момент, її слід негайно оновити.

Для цього можна використовувати reactive об’єкт: він такий самий, як проксі, що повертається useState, але не пов’язаний з жодним компонентом. Компонент може потім виконати useState для нього, щоб підписатися на його зміни.

  1. Оновіть службу статистики, щоб вона перезавантажувала дані кожні 10 хвилин (для перевірки використовуйте кожні 10 хвилин!).

  2. Змініть його, щоб він повертав об’єкт реактивний. Перезавантаження даних має оновити реактивний об’єкт на місці.

  3. Компонент Dashboard тепер може використовувати його з useState

8. Відкладене завантаження інф. панелі

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

Один зі способів зробити це – використовувати LazyComponent@web/core/assets) як проміжний елемент, який завантажить пакет ресурсів перед відображенням нашого компонента.

Example

example_action.js:

export class ExampleComponentLoader extends Component {
    static components = { LazyComponent };
    static template = xml`
        <LazyComponent bundle="'example_module.example_assets'" Component="'ExampleComponent'" />
    `;
}

registry.category("actions").add("example_module.example_action", ExampleComponentLoader);
  1. Перемістіть усі ресурси інф. панелі у підпапку /dashboard, щоб їх було легше додавати до пакету.

  2. Створіть пакет ресурсів awesome_dashboard.dashboard, що містить весь вміст папки /dashboard.

  3. Змініть dashboard.js, щоб він зареєструвався в реєстрі lazy_components замість actions.

  4. У файлі src/dashboard_action.js створіть проміжний компонент, який використовує `LazyComponent, та зареєструйте його в реєстрі actions.

9. Зробити вашу інф. панель основною

Поки що у нас є гарна робоча інф. панель. Але вона наразі жорстко закодована в шаблоні інф. панелі. Що робити, якщо ми хочемо налаштувати нашу інф. панель? Можливо, деякі користувачі мають інші потреби та хочуть бачити інші дані.

Отже, наступним кроком є створення нашої інф. панелі універсальною: замість жорсткого кодування її вмісту в шаблоні, вона може просто перебирати список елементів інформаційної панелі. Але тоді виникає багато питань: як представити елемент інф. панелі, як його зареєструвати, які дані він повинен отримувати тощо. Існує багато різних способів проектування такої системи з різними компромісами.

У цьому посібнику ми вважатимемо елемент інф. панелі об’єктом із такою структурою:

const item = {
   id: "average_quantity",
   description: "Average amount of t-shirt",
   Component: StandardItem,
   // size and props are optionals
   size: 3,
   props: (data) => ({
      title: "Average amount of t-shirt by order this month",
      value: data.average_quantity
   }),
};

Значення description буде корисним у наступній вправі, щоб показати назви елементів, які користувач може додати до своєї інф. панелі. Число size є необов’язковим і просто описує розмір елемента інф. панелі, який буде відображатися. Нарешті, функція props є необов’язковою. Якщо її не вказано, ми просто надамо об’єкт statistics як дані. Але якщо його визначено, він буде використаний для обчислення конкретних властивостей для компонента.

Мета полягає в тому, щоб замінити вміст панелі інструментів наступним фрагментом коду:

<t t-foreach="items" t-as="item" t-key="item.id">
   <DashboardItem size="item.size || 1">
      <t t-set="itemProp" t-value="item.props ? item.props(statistics) : {'data': statistics}"/>
      <t t-component="item.Component" t-props="itemProp" />
   </DashboardItem>
</t>

Зверніть увагу, що наведений вище приклад містить дві розширені функції Owl: динамічні компоненти та динамічні властивості.

Наразі у нас є два типи компонентів елементів: картки з номерами, назвою та номером, і картки з круговою діаграмою з певною міткою та круговою діаграмою.

  1. Створіть та реалізуйте два компоненти: NumberCard та PieChartCard з відповідними властивостями.

  2. Створіть файл dashboard_items.js, в якому ви визначаєте та експортуєте список елементів, використовуючи NumberCard та PieChartCard для відтворення нашої поточної інф. панелі.

  3. Імпортуйте цей список елементів у наш компонент Dashboard, додайте його до компонента та оновіть шаблон, щоб використовувати t-foreach, як показано вище.

    setup() {
       this.items = items;
    }
    

А тепер наш шаблон інф. панелі є основним!

10. Зробити вашу інф. панель розширюваною

Однак вміст нашого списку елементів все ще жорстко закодований. Давайте виправимо це за допомогою реєстру:

  1. Замість експорту списку, зареєструйте всі елементи інф. панелі у реєстрі awesome_dashboard

  2. Імпортувати всі елементи реєстру awesome_dashboard у компонент Dashboard

Інф. панель керування тепер легко розширюється. Будь-який інший додаток Odoo, який хоче зареєструвати новий елемент на панелі керування, може просто додати його до реєстру.

11. Додавання та видалення елементів інф. панелі

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

Конфігурацію інф. панелі буде збережено як список видалених ідентифікаторів елементів.

  1. Додайте кнопку на інф. панелі зі значком шестерні, щоб позначити, що це кнопка налаштувань.

  2. Натискання на цю кнопку має відкрити діалогове вікно.

  3. У цьому діалоговому вікні ми хочемо побачити список усіх існуючих елементів інф. панелі, кожен з яких має прапорець.

  4. У нижньому колонтитулі має бути кнопка Apply. Натискання на неї призведе до створення списку всіх ідентифікаторів елементів, які не позначені.

  5. Ми хочемо зберегти це значення в локальному сховищі.

  6. І змініть компонент Dashboard, щоб фільтрувати поточні елементи, видаливши ідентифікатори елементів з конфігурації.

../../../_images/items_configuration.png

12. Йдемо далі

Ось список деяких невеликих покращень, які ви можете спробувати зробити, якщо у вас є час:

  1. Переконайтеся, що ваш додаток можна перекласти (за допомогою env._t).

  2. Клацання на ділянці кругової діаграми має відкрити список усіх замовлень відповідного розміру.

  3. Збережіть вміст інф. панелі у налаштуваннях користувача на сервері!

  4. Зробіть його адаптивним: у мобільному режимі кожна картка повинна займати 100% ширини.