XML šablonování
pro Nette Framework

25.3.2014

Píšeme aplikaci v XML

Nyní už víme vše, zejména jsme se seznámili s třídou DOMHelper a ukázali si praktické příklady, takže se můžeme pustit do psaní aplikace v XML.

Opakování je matkou moudrosti: XML šablonování vám dává možnost využívat v šablonách XML elementy, ke kterým si sami definujete, jaký HTML kód vygenerují. Máte k dispozici plnou sílu PHP a je jen na vás, co a jak uděláte. Já vám nyní ukážu pouze jednu z možností.

Tou možností je vytvořit si pro každou šablonu samostatný jmenný prostor. Pro @layout.xml to bude jeden prostor, pro Sign/in.xml druhý a tak dále. Každá šablona tak bude mít samostatnou třídu, která bude inteligentně vyhledána. Navíc budeme chtít různé elementy z různých šablon zpracovávat jednotně a také využívat společné operace, takže všechny třídy představující jmenné prostory budou mít nějakého společného rodiče a teprve ten bude dědit z třídy \Xml\DOMHelper.

Nyní si v @layout.xml definujeme element pro hlavní nadpis stránky a v odpovídající třídě to, o jaký nadpis půjde, jaký má mít text a jaké atributy. Nezapomeneme ani na metodu, která tento XML element obslouží.

<layout:title/>

protected $rename = array(
    "title" => "h1"
);
protected $attributes = array(
    "title" => array(["name" => "class", "value" => "nadpis"])
);
protected $content = array(
    "title" => "Nadpis"
);

public function titleElement($elem) {
    return parent::process($elem);
}

Z elementu title se stane h1, přidá se atribut class="nadpis" a mezi počáteční a koncovou značku text Nadpis. To vše zařídí rodičovská metoda process(), kterou už zvládnete sami. Metoda projde všechna tři pole a v případě, že v nich najde název daného elementu, provede požadované operace (ve správném pořadí, tj. přejmenování jako poslední).

Stejně můžete zpracovávat další XML element z šablony @layout.xml, ale i všech ostatních šablon (v odpovídajících třídách). Výhodou je, že veškeré texty, které se na stránce zobrazí, definujete přehledně na jednom místě, stejně tak atributy. V šabloně napíšete například <layout:linkPostTitle/> a definovat budete jinde, později a spolu se všemi ostatními elementy. Navíc elementy zpracováváte jednotně v rodičovské třídě, jedinou metodou.

Další výhodou je, že můžete jeden stejný element zobrazovat na více stránkách, ovšem definujete ho jen jednou. Jak? Výše uvedená pole definujete v rodičovské třídě (která tedy bude mít vlastnost xmlns stejně jako ostatní třídy jmenných prostorů a nebude abstraktní). Pak můžete například jak v šabloně pro zobrazení článku, tak v šabloně pro editaci článku napsat následující XML element s tím, že jeho definici (nejen text, který se zobrazí) máte jednotnou.

<base:linkPostDelete/>

Při psaní vaší aplikace v XML jsou vám k dispozici všechna Latte makra, která podporuje knihovna nette (a makra samozřejmě můžete psát i standardně, z pohledu XML jde o textové uzly). Starat se nemusíte ani o změny syntaxe Latte maker. Pro snadnou tvorbu formulářů můžete také využít knihovnu html, která rovnou zajišťuje i vazbu formulářů na data.

A to je opravdu vše. Nezbývá, než vám popřát mnoho zdaru a příjemný vývoj webových aplikací v jazyce XML.

24.3.2014

Compiler - poslední krok

Kromě parseru má XML šablonování také compiler (stejně jako Latte). Compiler převádí DOM strom na řetězec a stará se o to, aby programátor nemusel nic řešit.

Šablony stránek psaných v jazyce XML budou prakticky vždy vypadat následovně:

<layout:root xmlns:layout="layout" xmlns:nette="nette"> ...
    <layout:title/> ...
    <nette:include name="content"/> ...
</layout:root>

Programátor si definuje jmenné prostory, které následně bude využívat. Deklaraci může provést v kořenovém uzlu, ale také kdekoliv jinde. Podle jmenných prostorů se pak budou volat metody z daných tříd a programátor bude XML elementy modifikovat dle libosti - element title přejmenuje na h2 a vloží do něj nějaký text, element include se díky knihovně nette transformuje na Latte makro. Jaký ale v každém případě bude požadovaný výstup?

1. Bez kořenového uzlu, který v HTML v mnoha případech nemá využití a programátor ho uvádí jen proto, aby si deklaroval jmenné prostory a aby XML bylo validní.

2. Bez deklarací jmenných prostorů, které mohou být uvedeny kdekoliv v dokumentu. Atributy xmlns v HTML nemají využití.

3. Bez prefixů elementů, které je přiřazují do jmenných prostorů a se kterými by prohlížeč elementům nerozuměl.

Toto vše řeší compiler, programátor se tím nemusí zabývat. Výstupem je obsah kořenového uzlu bez jmenných prostorů se správným kódováním českých znaků, HTML entit a podobně. O formátování výstupu se stará samotná metoda saveHTML(), kterou by do budoucna bylo možné nahradit metodou vlastní.

23.3.2014

Vazba formulářů na data II

Už víme, co je vazba formulářů na data, tak si pojďme povědět více o tom, jak funguje.

Vazba se vytváří pouze pro input elementy, kterým je možné v šabloně nastavit hodnotu následovně:

<html:input name="name" value="value"/>

Pro element textarea to takto nelze a nefunguje ani uvedení hodnoty mezi počáteční a koncovou značku (lze to tedy vůbec?). Do budoucna v každém případě bude možné vazbu formulářů na data rozšířit, aby zvládala i další elementy.

Vazbu hledá parser, a to v hodnotě atributu value. Jde o jednoduchý konečný automat, který nejprve čeká na začátek Latte makra, přičemž zohledňuje i případně změny syntaxe. Poté vyhledává řetězce ve tvaru $trida->vnorena->vlastnost a ukládá je spolu s identifikací formuláře a daného input elementu do cache. Vazba se pak z cache čte po odeslání formuláře, a to v metodě, která se volá před všemi uživatelem definovanými metodami pro obsluhu úspěšného odeslání formuláře.

Parser zvládá jednoduché, ale i složité případy a je také schopný vytvořit vazbu jednoho input elementu na více dat. Uvedu několik příkladů:

<html:input name="name" value="{$trida->vlastnost}"/>
<html:input name="name" value="toto $nenajde, toto jo {$trida->vlastnost.'text'}"/>
<html:input name="name" value="{{$trida->vlastnost}}{{$trida->vlastnost2}}"/>

Je třeba, aby hodnoty vyplněné ve formuláři bylo možné do proměnných zapsat. V případě volání metod tak není možné učinit do lokálních proměnných, private vlastností, a to ani těch, které jsou vráceny public metodami. Public metoda vracející public vlastnost pak postrádá význam, ledaže by nastavovala hodnotu, což snad nikoho nenapadne a už vůbec takovou metodu nebude volat z šablony. Z těchto důvodů parser ve vazbě formulářů na data nepodporuje volání metod a v případě, že nalezne $trida->metoda(), tak vyhodí výjimku.

Co když ale máme formulář, chceme vytvořit vazbu na data, ale v některých input elementech voláme metody? Pak takové inputy z vazby vyloučíme. I to parser zvládá.

$this->createRelation($elem, ["input1", "input2"]);

A zvládá to i knihovna html.

<html:form name="name" exclude="input1, input2"> ...
</html:form>

22.3.2014

Vazba formulářů na data

V rámci XML šablonování lze využít vazbu formulářů na data. O co se jedná? Představte si, že jste v presenteru běžným způsobem vytvořili formulář a zaregistrovali metodu, která se zavolá po jeho odeslání. Jaká bude první věc, kterou v této metodě budete dělat? Tato:

$values = $form->getValues();

Získáte hodnoty, které uživatel do formuláře vyplnil, a budete s nimi pracovat. V pořádku. Nicméně XML šablonování toto umí udělat za vás.

V Nette existuje jednosměrná vazba, kdy si data z presenteru vložíte do šablony a v ní je poté používáte. Vazba je jednosměrná, protože nefunguje opačným směrem. XML šablonování tuto vazbu dodává.

Vytvořme si formulář, například jednoduše pomocí knihovny html, a naplňme input elementy hodnotami.

<html:form name="writerForm">
    <html:input name="username" value="{$userManager->username}"/>
    <html:input name="email" value="{$userManager->email}"/>
    <html:input name="password" value="{$userManager->password}"/>
    <html:input name="send" label="no"/>
</html:form>

Když nyní formulář vyplníte a odešlete, hodnoty budou okamžitě uloženy v objektu userManager. Okamžitě a samy. V metodě, která se volá po odeslání formuláře, je nemusíte vyzvedávat. Pokud userManager je například třída pracující s databázovou tabulkou users, můžete rovnou zavolat metodu userManager->saveUser(), která uživatele uloží do databáze. Nebo hodnoty využít jinak, je to na vás.

Podobu formuláře v šabloně už jsme si ukázali, doplním ještě stručnou ukázku třídy userManager (opravdu stručnou, ve skutečnosti budete heslo samozřejmě hashovat) a metody, která se volá po odeslání formuláře.

class UserManager extends BaseManager {
    public $username = "";
    public $email = "";
    public $password = "";
    public function saveUser() {
        $this->getTable()->insert(array(
            "username" => $this->username,
            "email" => $this->email,
            "password" => $this->password));
}

public function writerFormSucceeded(Form $form) {
    $this->userManager->saveWriter();
    $this->redirect('this');
}

To je vše. Aby vazba fungovala, musíte provést jen dvě věci. Formulář vytvořit nikoliv jako instanci třídy Nette\Application\UI\Form, ale jako instanci třídy Xml\UI\Form. Té předáte instanci presenteru. Anebo použijete metodu createForm() připravenou v třídě Xml\UI\Presenter.

$form = new \Xml\UI\Form($this);
nebo
$form = $this->createForm();

Pak už stačí jen v metodě obsluhující kořenový XML element formuláře zavolat metodu createRelation() a vrátit hodnotu FORM_RELATION. Stejně to dělá i knihovna html.

public function formElement($elem) {
    $this->createRelation($elem);
    return $this::FORM_RELATION;
}

Protože šablony se v Nette parsují jen jednou a poté se čtou z cache, také vazby formulářů na data se ukládají do cache. Pokud navíc za účelem vývoje a testování promazáváte v souboru bootstrap.php cache při každém požadavku voláním metody Xml\Engine::cleanCache(), je zaručeno, že cache se smaže až poté, co se aplikují uložené vazby. Tedy i v tomto případě vazba formulářů na data bude fungovat.

21.3.2014

Změny syntaxe Latte maker

Při transformaci XML elementu na Latte makro se parser stará o to, aby byla použita správná syntaxe maker. Latte ji totiž umožňuje změnit, a to za běhu, přímo v šabloně. Parser tedy makro obalí do jedné složené závorky, dvou složených závorek apod. podle aktuální syntaxe. Jinými slovy moje XML šablonování rozpoznává změny syntaxe maker stejně, jako to dělá Latte (dělá to úplně jinak, ale funguje to úplně stejně).

Představme si, že k žádné změně syntaxe nedošlo. V takovém případě se nějaký XML element transformuje na makro následovně:

<nette:print variable="$var"/> ---> {$var}

V případě, že však dříve došlo ke změně syntaxe, například zápisem {syntax double}, transformace bude, aniž by programátor cokoliv měnil, vypadat úplně jinak. Takhle:

<nette:print variable="$var"/> ---> {{$var}}

Parser si také poradí se situací, kdy je element párový a syntaxe se změní uvnitř něj.

<nette:if cond="$user->loggedIn">
    {syntax double} ...
</nette:if>

Mohlo by se zdát, že element se na makro promění celý v jeden okamžik, ovšem není tomu tak. Nejprve je transformována počáteční značka, poté jsou zpracováni potomci a teprve při skutečném příchodu na koncovou značku, kdy už parser ví, jaká je aktuální syntaxe, dojde k transformaci této značky. Výsledek je tedy korektní:

{if $user->loggedIn}
    {syntax double} ...
{{/if}}

Rovněž fungují i změny pomocí atributu n:syntax.

Toto vše se děje bez účasti programátora, který se nemusí o nic starat. Metoda pro transformaci XML elementu na Latte makro vypadá pořád stejně, správnou syntaxi zařídí parser.

Ukažme si, jak je vytvoření makra jednoduché (a jak to s menším doplněním dělá i knihovna nette):

public function ifElement($elem) {
    $value = $this->getAttributeValue($elem, "cond");
    $this->createMacro($elem, "if " . $value, "/if");
    return $this::MACRO_CREATED;
}

  starší →