Dart + Polymer GUG Hackathon: Part dva
Posledně jsem se rozepsal nad mez odolnosti roztěkaného novodobého čtenáře, tak jsem se raději utnul a tři ozvěny Polymer Hackathonu jsem si ještě pošetřil. Jdeme na to.
core-icon
Na zahřátí si dáme něco lehčího - core-icon. To je pěkná ukázka toho, jak má člověk dělat Polymer elementy. Krásně znovupoužitelné atomické tagy, které můžete plácnout kdykoliv a kdekoliv. Konkrétně core-icon do mrtě rozebral Rob Dodson na Google Developers Youtube kanálu:
Já jsem ji použil na vykreslení indikátoru velikosti planety:
<tr template repeat="{{ planet in planets | enumerate }}" ... >
<td>{{planet.index + 1}}</td>
<th class="left">{{planet.value.name}}</th>
<td class="right">{{planet.value.distance}} parsecs</td>
<td><core-icon icon="check-circle-blank"
style="width: {{planet.value.size * 10}}px; height: {{planet.value.size * 10}}px"></core-icon></td>
...
</tr>
Povšiměte si především kreativní práce s width a height. Polopatické, že? V reálném nasazení bych to možná nedělal takhle, minimálně bych tam nedával px ale alespoň em. Ikona krasně scaluje, protože SVG.
Banalita, ale potěší.
MMCH - příklad taky ukazuje, jak iterovat přes seznam objektů s ukládáním pořadového čísla - “| enumerate”. Kdybych iteroval bez “| enumerate”, budu místo planet.value.distance psát planet.distance, ale na planet.index bych mohl zapomenout.
Observables
Jak vidno, vypisuji v příkladu seznam planet. Seznam je utřízený podle vzdálenosti. Jakmile hráč “zawarpuje” jinam, seznam se překreslí. Abych mohl v seznamu použít “{{planets}}” a přitom se mohl spolehnout, že se seznam přerenderuje vždy když ho přetřídím, musím z něj udělat “Observable”.
sortedPlanets = toObservable(game.planets);
Prosté:
@observable List<Planet> sortedPlanets;
… nám zajistí sledování toho, že se změnila hodnota vlastnosti sortedPlanets (změna na jiný list), ale už nebude sledovat změny, které se dějí s prvky listu.
Pokud chci sledovat změny, které se dějí v doménovém modelu (například class Player) musím si model mírně zasvinit podporou Observables.
class Player extends Observable {
@observable int money = 5000;
@observable int fuel = 1000;
...
}
Když pak v šabloně použiju {{game.player.money}}, můžu si být jistý, že kdykoliv změním money, přerenderuje se i šablona.
No jo, ale jak je to vlastně uděláno? Jaký démon hlubin pekelných sleduje změny té proměnné? Přece Polymer neustále nescanuje stav proměnných které vykreslil? Pochopitelně že ne. Kdybych to napsal nějak takhle:
get money => _money;
set money(val) {
_money = notifyPropertyChange(#money, _money, val);
}
Tak už by to taková záhada nebyla, že jo. No a to se právě děje. Bežte do ‘dart:observe’ a podívejte se na ‘transformer.dart’. Během buildu se pomocí transformeru vaše magické @observable přepíše na get, set a notifyPropertyChange.
Prosim Vás. Nemyslete si, že jsem šel studovat zdrojáky jaderných knihoven Dartu. Přišel jsem na to tak, že jsem napsal:
@observable Neco neco = nejakaChyba;
Transformer to přepsal a kompilátor pak nahlásil, že to takhle nejde - a při té příležitosti napráskal, jak to ten transformer vlastně přepsal.
Zákeřné optimalizace změn DOMu
Na závěr jsem si nechal špek největší. Hele nahoru, tam jak iteruju přes planety a vypisuju si je do tabulky.
planet.value.distance je vzdálenost planety od hráče. Player je součástí Game, Planet taky, Planet zná Game tudíš Planet zná i hráče a může říct jak je od něj daleko. Je asi sporné, jestli má Planet tuto metodu poskytovat, jestli by to neměl dělat nějaký nástroj, který bude nad modelem operovat. Ale - udělal jsem to takhle.
No jo, ale distance není žádná observable property, která by notifikovala svoje změny. Je to složitější výpočet. Z pohledu Polymer template se tedy nemění. To se projevovalo mysteriózní bugou. Pokud hráč odwarpoval jinam, planety jsem zesortoval a výpis planety se automaticky překreslil. Protože:
sortedPlanets = toObservable(game.planets);
Ale pokud se pořadí planety v listu nezměnilo, její konkrétní řádek se vůbec nepřerenderoval, zůstal tak jak byl - sice se změnila distance, ale ne z pohledu Polymeru. To podle mě na straně templatů obnáší netriviální/obdivuhodné optimalizace změn DOMu … ale jak z toho ven?
Jednou cestou je ComputedProperty. S tím ale praktickou zkušenost nemám. Během hackathonu jsem se vzmohl jen na dirty hack - v Polymer template jsem použil množství paliva Playera, tím jsem zaregistroval template k poslouchání změn jeho stavu. Když se změní palivo, template řádku planety se překreslí. A tedy nakreslí i správnou novou vzdálenost - změna paliva = změna polohy.
<td ... data-dirty-hack-fuel="{{player.fuel}}" .../>
Tak takhle to nedělejte, m’key? Je to velmi poučné, ale naprosto špinavé :-)
No a to je všechno. Příště se donutím ke sdílení čerstvých zkušeností z tvorby we.are.hiring.cz. Pokud tedy nebudu plný dojmů ze zítřejšího DevFest.