1. fejezet: Owl komponensek¶
Ez a fejezet bemutatja az Owl keretrendszert, amely egy Odoo-ra szabott komponensrendszer. Az OWL fő építőelemei a komponensek és a sablonok.
Az Owl-ban a felhasználói felület minden részét egy komponens kezeli: ezek tartalmazzák a logikát és meghatározzák a sablonokat, amelyeket a felhasználói felület megjelenítésére használnak. Gyakorlatilag egy komponenst egy kis JavaScript osztály képvisel, amely a Component osztályból származik.
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, amelyeket ebben a tutorial introduction leírtunk.
Javaslat
Ha a Chrome-ot használja webböngészőként, telepítheti az Owl Devtools kiterjesztést. Ez a kiterjesztés számos funkciót biztosít, amelyek segítenek megérteni és profilozni bármely Owl alkalmazást.
In this chapter, we use the awesome_owl addon, which provides a simplified environment that
only contains Owl and a few other files. The goal is to learn Owl itself, without relying on Odoo
web client code.
The solutions for each exercise of the chapter are hosted on the official Odoo tutorials repository. It is recommended to try to solve them first without looking at the solution!
Példa: egy Counter komponens¶
Először nézzünk meg egy egyszerű példát. Az alábbi Counter komponens egy olyan komponens, amely egy belső számértéket tart fenn, megjeleníti azt, és frissíti, amikor a felhasználó a gombra kattint.
import { Component, useState } from "@odoo/owl";
export class Counter extends Component {
static template = "my_module.Counter";
setup() {
this.state = useState({ value: 0 });
}
increment() {
this.state.value++;
}
}
A Counter komponens megadja annak a sablonnak a nevét, amely a html-jét képviseli. Ez XML-ben van írva a QWeb nyelv használatával:
<templates xml:space="preserve">
<t t-name="my_module.Counter">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</t>
</templates>
1. Egy számláló megjelenítése¶
Első gyakorlatként módosítsuk az awesome_owl/static/src/ helyen található Playground komponenst, hogy számlálóvá alakítsuk. Az eredmény megtekintéséhez a böngészőjével elérheti a /awesome_owl útvonalat.
Modify
playground.jsso that it acts as a counter like in the example above. KeepPlaygroundfor the class name. You will need to use the useState hook so that the component is re-rendered whenever any part of the state object that has been read by this component is modified.Ugyanebben a komponensben hozzon létre egy
incrementmetódust.Módosítsa a
playground.xmlsablont úgy, hogy megjelenítse a számláló változót. Használja a t-esc funkciót az adatok kiíratásához.Adjon hozzá egy gombot a sablonhoz, és adjon meg egy t-on-click attribútumot a gombban, hogy az
incrementmetódust hívja meg, amikor a gombra kattintanak.
Javaslat
The Odoo JavaScript files downloaded by the browser are minified. For debugging purpose, it’s easier when the files are not minified. Switch to debug mode with assets so that the files are not minified.
Ez a gyakorlat bemutatja az Owl egyik fontos funkcióját: a reactivity system-et. A useState függvény egy értéket proxyba csomagol, így az Owl nyomon tudja követni, melyik komponensnek melyik állapotrészre van szüksége, így frissíthető, amikor egy érték megváltozik. Próbálja meg eltávolítani a useState függvényt, és nézze meg, mi történik.
2. Counter kivonása egy alkomponensbe¶
Jelenleg a számláló logikája a Playground komponensben van, de nem újrafelhasználható. Nézzük meg, hogyan lehet belőle sub-component létrehozni:
Vonja ki a számláló kódját a
Playgroundkomponensből egy újCounterkomponensbe.Először megteheti ugyanabban a fájlban, de ha kész, frissítse a kódját, hogy a
Countersaját mappájába és fájljába kerüljön. Importálja relatívan a./counter/counterútvonalról. Győződjön meg róla, hogy a sablon saját fájlban van, ugyanazzal a névvel.Use
<Counter/>in the template of thePlaygroundcomponent to add two counters in your playground.
Javaslat
Konvenció szerint a legtöbb komponens kódja, sablonja és css-e ugyanazzal a snake-case névvel kell rendelkezzen, mint a komponens. Például, ha van egy TodoList komponensünk, akkor a kódjának a todo_list.js, todo_list.xml és szükség esetén a todo_list.scss fájlokban kell lennie.
3. Egy egyszerű Card komponens¶
A komponensek valóban a legtermészetesebb módja annak, hogy egy bonyolult felhasználói felületet több újrahasznosítható darabra osszunk. Ahhoz azonban, hogy valóban hasznosak legyenek, szükséges, hogy képesek legyünk információt közvetíteni közöttük. Nézzük meg, hogyan tud egy szülő komponens információt biztosítani egy alkomponens számára attribútumok (leggyakrabban props néven ismertek) használatával.
Ennek a gyakorlatnak a célja egy Card komponens létrehozása, amely két prop-ot vesz fel: title és content. Például, így lehetne használni:
<Card title="'my title'" content="'some content'"/>
A fenti példa némi html-t kell, hogy előállítson bootstrap használatával, amely így néz ki:
<div class="card d-inline-block m-2" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">my title</h5>
<p class="card-text">
some content
</p>
</div>
</div>
Hozz létre egy
CardkomponenstImportáld a
Playground-ba és jeleníts meg néhány kártyát a sablonjában
4. markup használata html megjelenítéséhez¶
If you used t-esc in the previous exercise, then you may have noticed that Owl automatically escapes
its content. For example, if you try to display some html like this: <Card title="'my title'" content="this.html"/>
with this.html = "<div>some content</div>"",
the resulting output will simply display the html as a string.
Ebben az esetben, mivel a Card komponens bármilyen tartalom megjelenítésére használható, ésszerű lehetővé tenni a felhasználó számára, hogy némi HTML-t jelenítsen meg. Ezt a t-out direktívával lehet megtenni <https://github.com/odoo/owl/blob/master/doc/reference/templates.md#outputting-data>`_.
Azonban tetszőleges tartalom HTML-ként való megjelenítése veszélyes, mivel rosszindulatú kód befecskendezésére használható, ezért alapértelmezés szerint az Owl mindig escape-eli a karakterláncot, hacsak nem jelölték meg kifejezetten biztonságosként a markup függvénnyel.
Frissítse a
Card-ot at-outhasználatáraFrissítse a
Playground-ot amarkupimportálására, és használja azt néhány HTML értékenGyőződjön meg arról, hogy látja, hogy a normál karakterláncok mindig escape-eltek, ellentétben a markupolt karakterláncokkal.
Megjegyzés
A t-esc direktíva továbbra is használható az Owl sablonokban. Ez kissé gyorsabb, mint a t-out.
5. Props validáció¶
A Card komponensnek implicit API-ja van. Két karakterláncot vár a props-ban: a title és a content. Tegyük ezt az API-t kifejezettebbé. Hozzáadhatunk egy props definíciót, amely lehetővé teszi az Owl számára, hogy validációs lépést hajtson végre dev módban. A dev módot aktiválhatja az App konfigurációban (de alapértelmezés szerint aktiválva van az awesome_owl játszótéren).
Jó gyakorlat minden komponensnél props validációt végezni.
Adjon hozzá props validációt a
Cardkomponenshez.Nevezze át a
titletulajdonságokat valami másra a játszótér sablonban, majd ellenőrizze a böngésző fejlesztői eszközeinek Konzol fülén, hogy látható-e hiba.
6. Két Counter összege¶
Egy korábbi gyakorlatban láttuk, hogy a props használható információk átadására egy szülő komponensből egy gyermek komponensbe. Most nézzük meg, hogyan tudunk információt közvetíteni az ellenkező irányba: ebben a gyakorlatban két Counter komponenst szeretnénk megjeleníteni, és alattuk az értékeik összegét. Tehát a szülő komponensnek (Playground) értesülnie kell, amikor az egyik Counter értéke megváltozik.
Ezt úgy lehet megtenni, hogy egy callback prop használatával: egy tulajdonság, amely egy visszahívásra szánt függvény. A gyermek komponens dönthet úgy, hogy bármilyen argumentummal meghívja ezt a függvényt. Esetünkben egyszerűen hozzáadunk egy opcionális onChange tulajdonságot, amelyet akkor hívunk meg, amikor a Counter komponens növekszik.
Adjon hozzá tulajdonság-ellenőrzést a
Counterkomponenshez: fogadnia kell egy opcionálisonChangefüggvény tulajdonságot.Frissítse a
Counterkomponenst, hogy hívja meg azonChangetulajdonságot (ha létezik), amikor növekszik.Módosítsa a
Playgroundkomponenst, hogy fenntartson egy helyi állapotértéket (sum), amely kezdetben 2-re van állítva, és jelenítse meg a sablonjában.Valósítson meg egy
incrementSummetódust aPlayground-ban.Adja meg ezt a metódust tulajdonságként két (vagy több!) al
Counterkomponensnek.
Fontos
There is a subtlety with callback props: they usually should be defined with the .bind
suffix. See the documentation.
7. Egy teendőlista¶
Most fedezzük fel az Owl különböző funkcióit egy teendőlista létrehozásával. Két komponensre van szükségünk: egy TodoList komponensre, amely megjeleníti a TodoItem komponensek listáját. A teendők listája egy állapot, amelyet a TodoList-nek kell fenntartania.
Ebben az útmutatóban egy todo egy objektum, amely három értéket tartalmaz: egy id (szám), egy description (sztring) és egy isCompleted (logikai) jelzőt:
{ id: 3, description: "buy milk", isCompleted: false }
Create a
TodoListand aTodoItemcomponents.A
TodoItemkomponensnek egytodo-t kell kapnia propként, és meg kell jelenítenie annakid-ját ésdescription-ját egydiv-ben.Egyelőre kódolja be a teendők listáját:
// in TodoList this.todos = useState([{ id: 3, description: "buy milk", isCompleted: false }]);
Use t-foreach to display each todo in a
TodoItem.Display a
TodoListin the playground.Add props validation to
TodoItem.
Javaslat
Since the TodoList and TodoItem components are so tightly coupled, it makes
sense to put them in the same folder.
Megjegyzés
The t-foreach directive is not exactly the same in Owl as the QWeb python implementation: it
requires a t-key unique value, so that Owl can properly reconcile each element.
8. Használjon dinamikus attribútumokat¶
Jelenleg a TodoItem komponens nem mutatja vizuálisan, ha a todo be van fejezve. Tegyük ezt meg dinamikus attribútumok <https://github.com/odoo/owl/blob/master/doc/reference/templates.md#dynamic-attributes> használatával.
Adja hozzá a Bootstrap
text-mutedéstext-decoration-line-throughosztályokat aTodoItemgyökérelemhez, ha az be van fejezve.Change the hardcoded
this.todosvalue to check that it is properly displayed.
Bár az utasítás neve t-att (attribútumra), használható class érték beállítására (és HTML tulajdonságokra, mint például egy input value értéke).
Javaslat
Az Owl lehetővé teszi statikus osztályértékek kombinálását dinamikus értékekkel. A következő példa a vártnak megfelelően fog működni:
<div class="a" t-att-class="someExpression"/>
Lásd még: Owl: Dinamikus osztály attribútumok
9. Feladat hozzáadása¶
Eddig a listánkban lévő feladatok kódoltak. Tegyük hasznosabbá azáltal, hogy lehetővé tesszük a felhasználó számára, hogy feladatot adjon a listához.
Remove the hardcoded values in the
TodoListcomponent:this.todos = useState([]);
Adjon hozzá egy bemeneti mezőt a feladatlista fölé a következő helyőrzővel: Adjon meg egy új feladatot.
Adjon hozzá egy eseménykezelőt a
keyupeseményhez, amelynek neveaddTodo.Valósítsa meg az
addTodo-t, hogy ellenőrizze, ha az enter billentyűt lenyomták (ev.keyCode === 13), és ebben az esetben hozzon létre egy új teendőt a bemeneti mező aktuális tartalmával leírásként, majd törölje a bemeneti mező összes tartalmát.Győződjön meg róla, hogy a teendőnek egyedi azonosítója van. Ez lehet egy számláló, amely minden teendőnél növekszik.
Bónusz pont: ne tegyen semmit, ha a bemeneti mező üres.
Lásd még
Elmélet: Komponens életciklus és horgok¶
Eddig egy példa horgos függvényre: useState. A horog egy speciális függvény, amely bekapcsolódik a komponens belső működésébe. A useState esetében egy proxy objektumot generál, amely a jelenlegi komponenshez kapcsolódik. Ezért kell a horgos függvényeket a setup metódusban meghívni, és nem később!
An Owl component goes through a lot of phases: it can be instantiated, rendered, mounted, updated, detached, destroyed… This is the component lifecycle. The figure above show the most important events in the life of a component (hooks are shown in purple). Roughly speaking, a component is created, then updated (potentially many times), then is destroyed.
Az Owl számos beépített hooks függvényt biztosít. Mindegyiket a setup függvényben kell meghívni. Például, ha kódot szeretne futtatni, amikor a komponens fel van szerelve, használhatja az onMounted hookot:
setup() {
onMounted(() => {
// do something here
});
}
Javaslat
Minden hook függvény use vagy on előtaggal kezdődik. Például: useState vagy onMounted.
10. Az input fókuszálása¶
Nézzük meg, hogyan érhetjük el a DOM-ot t-ref és useRef segítségével. A fő ötlet az, hogy meg kell jelölni a cél elemet a komponens sablonjában egy t-ref-el:
<div t-ref="some_name">hello</div>
Ezután a JS-ben elérheti a useRef hook segítségével. Azonban van egy probléma, ha belegondol: a komponens tényleges html eleme nem létezik, amikor a komponens létrejön. Csak akkor létezik, amikor a komponens fel van szerelve. De a hookokat a setup metódusban kell meghívni. Tehát a useRef egy olyan objektumot ad vissza, amely tartalmaz egy el (az elemhez) kulcsot, amely csak akkor van definiálva, amikor a komponens fel van szerelve.
setup() {
this.myRef = useRef('some_name');
onMounted(() => {
console.log(this.myRef.el);
});
}
Fókuszálja az előző feladat
input-ját. Ezt aTodoListkomponensből kell megtenni (vegye figyelembe, hogy van egyfocusmetódus az input html elemen).Bónusz pont: vonja ki a kódot egy specializált hook
useAutofocus-ba egy újawesome_owl/utils.jsfájlban.
Javaslat
A Ref-eket általában Ref utótaggal látják el, hogy egyértelmű legyen, hogy ezek speciális objektumok:
this.inputRef = useRef('input');
11. Teendők váltása¶
Now, let’s add a new feature: mark a todo as completed. This is actually trickier than one might
think. The owner of the state is not the same as the component that displays it. So, the TodoItem
component needs to communicate to its parent that the todo state needs to be toggled. One classic
way to do this is by adding a callback prop toggleState.
Add an input with the attribute
type="checkbox"before the id of the task, which must be checked if the stateisCompletedis true.Javaslat
Az Owl nem hoz létre
t-attdirektívával számított attribútumokat, ha azok hamis értékre értékelődnek ki.Adjon hozzá egy
toggleStatevisszahívási tulajdonságot aTodoItem-hez.Add a
changeevent handler on the input in theTodoItemcomponent and make sure it calls thetoggleStatefunction with the todo id.Működjön!
12. Teendők törlése¶
Az utolsó simítás az, hogy a felhasználó törölhessen egy teendőt.
Adjon hozzá egy új
removeTodovisszahívási tulajdonságot aTodoItem-hez.Insert
<span class="fa fa-remove"/>in the template of theTodoItemcomponent.Amikor a felhasználó rákattint, meg kell hívnia a
removeTodometódust.Működjön!
Javaslat
Ha tömböt használ a teendőlista tárolására, a JavaScript
splicefüggvényével eltávolíthat egy teendőt belőle.
// find the index of the element to delete
const index = list.findIndex((elem) => elem.id === elemId);
if (index >= 0) {
// remove the element at index from list
list.splice(index, 1);
}
13. Általános Card slotokkal¶
In a previous exercise, we built
a simple Card component. But it is honestly quite limited. What if we want
to display some arbitrary content inside a card, such as a sub-component? Well,
it does not work, since the content of the card is described by a string. It would
however be very convenient if we could describe the content as a piece of template.
This is exactly what Owl’s slot system is designed for: allowing to write generic components.
Módosítsuk a Card komponenst, hogy slotokat használjon:
Remove the
contentprop.Use the default slot to define the body.
Insert a few cards with arbitrary content, such as a
Countercomponent.(bonus) Add prop validation.
14. A kártya tartalmának minimalizálása¶
Végül adjunk hozzá egy funkciót a Card komponenshez, hogy érdekesebbé tegyük: szeretnénk egy gombot, amellyel a tartalmát váltogathatjuk (megmutatjuk vagy elrejtjük)
Adjon hozzá egy állapotot a
Cardkomponenshez, hogy nyomon kövesse, nyitva van-e (alapértelmezett) vagy semAdjon hozzá egy
t-if-et a sablonhoz, hogy feltételesen jelenítse meg a tartalmatAdjon hozzá egy gombot a fejlécben, és módosítsa a kódot, hogy a gomb megnyomásakor megváltozzon az állapot