2. fejezet: Készítsen egy irányítópultot

A bemutató első része bevezette Önt az Owl legtöbb ötletébe. Most itt az ideje, hogy megismerje az Odoo JavaScript keretrendszert teljes egészében, ahogyan azt a webes kliens használja.

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

A kezdéshez szüksége van egy futó Odoo szerverre és egy fejlesztői környezet beállítására. Mielőtt belekezdene a gyakorlatokba, győződjön meg róla, hogy követte az összes lépést, amelyet a bemutató bevezető leír. Ebben a fejezetben az awesome_dashboard bővítmény által biztosított üres irányítópultból indulunk ki. Fokozatosan adunk hozzá funkciókat, az Odoo JavaScript keretrendszer használatával.

Cél

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

The solutions for each exercise of the chapter are hosted on the official Odoo tutorials repository.

1. Egy új elrendezés

Most screens in the Odoo web client uses a common layout: a control panel on top, with some buttons, and a main content zone just below. This is done using the Layout component, available in @web/search/layout.

  1. Frissítse az AwesomeDashboard komponenst a awesome_dashboard/static/src/ helyen, hogy a Layout komponenst használja. Használhatja a {controlPanel: {} } kódot a Layout komponens display tulajdonságaihoz.

  2. Add a className prop to Layout: className="'o_dashboard h-100'"

  3. Adjon hozzá egy dashboard.scss fájlt, amelyben beállítja az .o_dashboard háttérszínét szürkére (vagy a kedvenc színére)

Nyissa meg a http://localhost:8069/web oldalt, majd nyissa meg az Awesome Dashboard alkalmazást, és tekintse meg az eredményt.

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

Elmélet: Szolgáltatások

In practice, every component (except the root component) may be destroyed at any time and replaced (or not) with another component. This means that each component internal state is not persistent. This is fine in many cases, but there certainly are situations where we want to keep some data around. For example, all Discuss messages should not be reloaded every time we display a channel.

Also, it may happen that we need to write some code that is not a component. Maybe something that process all barcodes, or that manages the user configuration (context, etc.).

Az Odoo keretrendszer meghatározza a szolgáltatás fogalmát, amely egy állandó kódrészlet, amely állapotot és/vagy funkciókat exportál. Minden szolgáltatás függhet más szolgáltatásoktól, és a komponensek importálhatnak egy szolgáltatást.

A következő példa regisztrál egy egyszerű szolgáltatást, amely 5 másodpercenként megjelenít egy értesítést:

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);

A szolgáltatások bármely komponens által elérhetők. Képzeljük el, hogy van egy szolgáltatásunk, amely fenntart egy megosztott állapotot:

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);

Ezután bármelyik komponens megteheti ezt:

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

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

2. Adjon hozzá néhány gombot a gyors navigációhoz

Az Odoo egyik fontos szolgáltatása az action szolgáltatás: képes végrehajtani mindenféle szabványos műveletet, amelyet az Odoo határoz meg. Például, így hajthat végre egy komponens egy műveletet az xml azonosítója alapján:

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

Most adjunk hozzá két gombot az irányítópultunkhoz:

  1. Egy Customers gomb, amely megnyit egy kanban nézetet az összes ügyféllel (ez a művelet már létezik, így használja az xml azonosítóját).

  2. A button Leads, which opens a dynamic action on the crm.lead model with a list and a form view. Follow the example of this use of the action service.

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

3. Add a dashboard item

Most javítsuk a tartalmunkat.

  1. Create a generic DashboardItem component that display its default slot in a nice card layout. It should take an optional size number props, that default to 1. The width should be hardcoded to (18*size)rem.

  2. Add two cards to the dashboard. One with no size, and the other with a size of 2.

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

4. Hívja meg a szervert, adjon hozzá néhány statisztikát

Let’s improve the dashboard by adding a few dashboard items to display real business data. The awesome_dashboard addon provides a /awesome_dashboard/statistics route that is meant to return some interesting information.

To call a specific controller, we need to use the rpc function. It only exports a single function that perform the request: rpc(route, params, settings). A basic request could look like this:

import { rpc } from "@web/core/network/rpc";
// ...

setup() {
   onWillStart(async () => {
      const result = await rpc("/my/controller", {a: 1, b: 2});
   })
   // ...
}
  1. Update Dashboard so that it uses the rpc function and call the statistics route /awesome_dashboard/statistics.

  2. Jelenítsen meg néhány kártyát a műszerfalon, amelyek tartalmazzák:

    • Az új rendelések száma ebben a hónapban

    • Az új rendelések teljes összege ebben a hónapban

    • Az egy rendelésre jutó pólók átlagos száma ebben a hónapban

    • A lemondott rendelések száma ebben a hónapban

    • Az átlagos idő, amíg egy rendelés «új»-ból «elküldött» vagy «lemondott» állapotba kerül

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

Lásd még

Code: rpc

5. Hálózati hívások gyorsítótárazása, szolgáltatás létrehozása

Ha megnyitja a böngészője fejlesztői eszközeinek Hálózat fülét, látni fogja, hogy a /awesome_dashboard/statistics hívás minden alkalommal megtörténik, amikor az ügyfél művelet megjelenik. Ez azért van, mert az onWillStart horog minden alkalommal meghívódik, amikor a Dashboard komponens betöltődik. De ebben az esetben azt szeretnénk, hogy ez csak az első alkalommal történjen meg, így valójában szükségünk van arra, hogy némi állapotot fenntartsunk a Dashboard komponensen kívül. Ez egy remek példa egy szolgáltatás használatára!

  1. Regisztráljon és importáljon egy új awesome_dashboard.statistics szolgáltatást.

  2. Biztosítania kell egy loadStatistics függvényt, amely meghívásakor végrehajtja a tényleges rpc-t, és mindig ugyanazt az információt adja vissza.

  3. Use the memoize utility function from @web/core/utils/functions that allows caching the statistics.

  4. Használja ezt a szolgáltatást a Dashboard komponensben.

  5. Check that it works as expected.

6. Kördiagram megjelenítése

Mindenki szereti a diagramokat (!), ezért adjunk hozzá egy kördiagramot a műszerfalunkhoz. Ez megjeleníti az egyes méretekre (S/M/L/XL/XXL) eladott pólók arányát.

Ehhez a gyakorlathoz a Chart.js könyvtárat fogjuk használni. Ez a grafikon nézet által használt diagram könyvtár. Azonban alapértelmezés szerint nincs betöltve, ezért hozzá kell adnunk az eszközcsomagunkhoz, vagy késleltetve kell betöltenünk. A késleltetett betöltés általában jobb, mivel a felhasználóinknak nem kell minden alkalommal betölteniük a chartjs kódot, ha nincs rá szükségük.

  1. Create a PieChart component.

  2. Az onWillStart metódusában töltse be a chartjs-t, használhatja a loadJs függvényt a /web/static/lib/Chart/Chart.js betöltéséhez.

  3. Use the PieChart component in a DashboardItem to display a pie chart that shows the quantity for each sold t-shirts in each size (that information is available in the /statistics route). Note that you can use the size property to make it look larger.

  4. A PieChart komponensnek egy vásznat kell megjelenítenie, és rajzolnia kell rá a chart.js használatával.

  5. Működjön!

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

7. Valós idejű frissítés

Since we moved the data loading in a cache, it never updates. But let us say that we are looking at fast moving data, so we want to periodically (for example, every 10min) reload fresh data.

This is quite simple to implement, with a setTimeout or setInterval in the statistics service. However, here is the tricky part: if the dashboard is currently being displayed, it should be updated immediately.

Ehhez használhatunk egy reactive objektumot: ez olyan, mint a useState által visszaadott proxy, de nincs összekapcsolva egyetlen komponenssel sem. Egy komponens ezután useState-et végezhet rajta, hogy feliratkozzon a változásaira.

  1. Update the statistics service to reload data every 10 minutes (to test it, use 10s instead!)

  2. Módosítsa úgy, hogy egy reactive objektumot adjon vissza. Az adatok újratöltése frissítenie kell a reaktív objektumot a helyén.

  3. A Dashboard komponens most már használható a useState-tel

8. A dashboard késleltetett betöltése

Képzeljük el, hogy a dashboardunk meglehetősen nagyra nőtt, és csak néhány felhasználónk számára érdekes. Ebben az esetben érdemes lehet késleltetve betölteni a dashboardunkat és az összes kapcsolódó erőforrást, így csak akkor fizetjük meg a kód betöltésének költségét, amikor valóban meg akarjuk nézni.

One way to do this is to use LazyComponent (from @web/core/assets) as an intermediate that will load an asset bundle before displaying our component.

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. Move all dashboard assets into a sub folder /dashboard to make it easier to add to a bundle.

  2. Create a awesome_dashboard.dashboard assets bundle containing all content of the /dashboard folder.

  3. Modify dashboard.js to register itself to the lazy_components registry instead of actions.

  4. In src/dashboard_action.js, create an intermediate component that uses LazyComponent and register it to the actions registry.

9. A műszerfalunk általánossá tétele

So far, we have a nice working dashboard. But it is currently hardcoded in the dashboard template. What if we want to customize our dashboard? Maybe some users have different needs and want to see other data.

So, the next step is to make our dashboard generic: instead of hard-coding its content in the template, it can just iterate over a list of dashboard items. But then, many questions come up: how to represent a dashboard item, how to register it, what data should it receive, and so on. There are many different ways to design such a system, with different trade-offs.

For this tutorial, we will say that a dashboard item is an object with the following structure:

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

The description value will be useful in a later exercise to show the name of items that the user can add to their dashboard. The size number is optional, and simply describes the size of the dashboard item that will be displayed. Finally, the props function is optional. If not given, we will simply give the statistics object as data. But if it is defined, it will be used to compute specific props for the component.

A cél az, hogy a műszerfal tartalmát a következő kódrészlettel helyettesítsük:

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

Note that the above example features two advanced features of Owl: dynamic components and dynamic props.

We currently have two kinds of item components: number cards with a title and a number, and pie cards with some label and a pie chart.

  1. Create and implement two components: NumberCard and PieChartCard, with the corresponding props.

  2. Create a file dashboard_items.js in which you define and export a list of items, using NumberCard and PieChartCard to recreate our current dashboard.

  3. Import that list of items in our Dashboard component, add it to the component, and update the template to use a t-foreach like shown above.

    setup() {
       this.items = items;
    }
    

És most, a műszerfal sablonunk általános!

10. Műszerfalunk kiterjeszthetővé tétele

Azonban az elemlista tartalma még mindig keménykódolt. Javítsuk ezt egy regiszter használatával:

  1. Lista exportálása helyett regisztrálja az összes műszerfal elemet egy awesome_dashboard regiszterben

  2. Importálja az összes elemet az awesome_dashboard regiszterből a Dashboard komponensbe

The dashboard is now easily extensible. Any other Odoo addon that wants to register a new item to the dashboard can just add it to the registry.

11. Műszerfal elemek hozzáadása és eltávolítása

Let us see how we can make our dashboard customizable. To make it simple, we will save the user dashboard configuration in the local storage so that it is persistent, but we don’t have to deal with the server for now.

A műszerfal konfigurációja eltávolított elemazonosítók listájaként lesz mentve.

  1. Add a button in the control panel with a gear icon to indicate that it is a settings button.

  2. Clicking on that button should open a dialog.

  3. In that dialog, we want to see a list of all existing dashboard items, each with a checkbox.

  4. There should be a Apply button in the footer. Clicking on it will build a list of all item ids that are unchecked.

  5. We want to store that value in the local storage.

  6. And modify the Dashboard component to filter the current items by removing the ids of items from the configuration.

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

12. Továbbhaladás

Íme néhány kisebb fejlesztés, amelyet megpróbálhat megvalósítani, ha van rá ideje:

  1. Győződjön meg róla, hogy az alkalmazása lefordítható (az env._t használatával).

  2. Clicking on a section of the pie chart should open a list view of all orders that have the corresponding size.

  3. Save the content of the dashboard in a user setting on the server!

  4. Make it responsive: in mobile mode, each card should take 100% of the width.