You are browsing the unmaintained documentation for old Nette 2.1. See documentation for current Nette.

Presentery

Seznámíme se s tím, jak se v Nette píší presentery a šablony. Po přečtení budete vědět:

  • jak funguje presenter
  • co jsou komponenty a persistentní parametry
  • jak se kreslí šablony

Životní cyklus presenteru

Už víme, že tzv. akce presenteru způsobí zavolání metody render<Akce>, tedy například renderShow. Ale to není zdaleka jediná metoda, která se volá. Při psaní presenterů můžeme mimo jiné vytvořit i následující metody:

Životní cyklus presenteru

startup()

Ihned po vytvoření presenteru se zavolá metoda startup(). Ta inicializuje proměnné nebo ověří uživatelská oprávnění.

Pokud si píšete vlastní metodu startup(), nezapomeňte zavolat předka parent::startup().

action<Action>()

Obdoba metody render<View>(). Jsou situace, kdy presenter provede určitý úkon (přihlásí uživatele, zapíše data do databáze) a poté přesměruje jinam. Dávat název render metodě, která nic nekreslí, by bylo nepěkné, proto existuje alternativa s názvem action.

Důležité je vědět, že action<Action>() se volá dříve, než metoda render<View>(). Může tedy i rozhodnout, aby se zavolala jiná metoda render pomocí příkazu $this->setView('jineView') (zavolá se renderJineView()).

handle<Signal>()

Metoda zpracovává tzv. signály neboli subrequesty. Určeno zejména pro komponenty a zpracování AJAXových požadavků. Na stejné úrovni se také provádí zpracování formulářů.

beforeRender()

Metoda beforeRender, jak už název napovídá, se volá před nám známou metodou render<View>() a může obsahovat například nastavení šablony, předání proměnných společných pro více view a podobně.

render<View>()

Obvykle nasype do šablony potřebná data.

shutdown()

Je vyvolána při ukončení životního cyklu presenteru.

Přesnější by bylo říci, že jsme si povídali o životním cyklu třídy Nette\Application\UI\Presenter, ze které se presentery dědí nejčastěji. Obecně je totiž presenter jakákoliv třída implementující trivální rozhraní Nette\Application\IPresenter.

Ukončení presenteru

Presenter můžeme během jeho životního cyklu kdykoliv ukončit. Obvykle tak učiníme proto, že chceme zamezit vykreslování šablony.

  • presenter rovnou ukončíme metodou $presenter->terminate()
  • presenter ukončíme a hned vyrenderujeme šablonu: $presenter->sendTemplate()
  • presenter ukončíme a odešleme payload: $presenter->sendPayload() (pro AJAX)
  • presenter ukončíme a odešleme vlastní odpověď: $presenter->sendResponse($response)

Presenter lze také ukončit přesměrováním či vyhozením výjimky BadRequestException.

Šablony

Cestu k souboru se šablonou odvodí presenter podle jednoduché logiky. Pro presenter Product a akci show zkusí, zda existuje jeden z těchto souborů:

- templates/Product/show.latte
- templates/Product.show.latte

Taktéž se pokusí dohledat layout (ten je nepovinný):

- templates/Product/@layout.latte
- templates/Product.@layout.latte
- templates/@layout.latte layout společný pro více presenterů

Způsob dohledávání šablon můžeme změnit přepsáním metod formatTemplateFiles nebo formatLayoutTemplateFiles.

Presentery a jejich komponenty předávají do šablon několik užitečných proměnných:

  • $basePath je absolutní URL cesta ke kořenovému adresáři (např. /CD-collection)
  • $baseUrl je absolutní URL ke kořenovému adresáři (např. http://localhost/CD-collection)
  • $user je objekt reprezentující uživatele
  • $presenter je aktuální presenter
  • $control je aktuální komponenta nebo presenter
  • $flashes pole zpráv zaslaných funkcí flashMessage()

O vykreslování šablon podrobněji v samostatné kapitole.

Ono to vlastně není nic těžkého! Pokud požaduji akci například Homepage:default, tak se

  1. vytvoří objekt třídy HomepagePresenter
  2. zavolá se metoda renderDefault() (existuje-li, ale nemusí)
  3. vykreslí se šablona např. templates/Homepage/default.latte s layoutem např. templates/@layout.latte

a v šabloně pak můžeme vytvořit odkaz na zmíněný Product:show($id), zhruba takto:

<a n:href="Product:show $productId">detail produktu</a>

Nechci to zakřiknout, ale tvorba aplikací v Nette bude pohodička.

Přesměrování

K přechodu na jiný presenter slouží metody redirect() a forward(), které mají velmi podobnou syntax jako zmíněná funkce link(). Například po odeslání formuláře a zápisu dat do databáze přesměrujeme na detail produktu zavoláním:

$this->redirect('Product:show', $id);

Zatímco forward() přejde na novou akci bez přesměrování, metoda redirect() přesměruje prohlížeč HTTP kódem 302 nebo 303. Chceme-li zvolit jiný kód, uvedeme jej ještě před názvem akce presenteru:

$this->redirect(301, 'Product:show', $id);

Na jinou URL mimo rozsah aplikace lze přesměrovat metodou redirectUrl()

$this->redirectUrl('https://nette.org');

Přesměrování okamžitě ukončí činnost presenteru vyhozením tzv. ukončovací výjimky Nette\Application\AbortException.

Před přesměrováním si občas chceme odeslat tzv. flash message, tedy zprávu, která se objeví po přesměrování v šabloně.

Chyba 404 a spol.

Pokud nemůžeme splnit požadavek z důvodu, že například záznam neexistuje v databázi, vyhodíme výjimku Nette\Application\BadRequestException, která představuje HTTP chybu 404:

public function renderShow($id)
{
	$product = $this->model->getProduct($id);
	if (!$product) {
		throw new Nette\Application\BadRequestException;
	}

	// ...
}

Pokud uživatel nemá oprávnění stránku vidět, vyhodíme výjimku Nette\Application\ForbiddenRequestException (chyba 403). Další chybové kódy lze uvést jako kód výjimky Nette\Application\BadRequestException.

Flash zprávy

Jde o zprávy obvykle informující o výsledku nějaké operace. Důležitým rysem flash zpráv je to, že jsou v šabloně k dispozici i po přesměrování. I po zobrazení zůstanou živé ještě další 3 sekundy – například pro případ, že by z důvodu chybného přenosu uživatel dal stránku obnovit – zpráva mu tedy hned nezmizí.

Stačí zavolat metodu flashMessage() a o předání do šablony se postará presenter. Prvním parametrem je text zprávy a nepovinným druhým parametrem její typ (error, warning, info apod.). Metoda flashMessage() vrací instanci flash zprávy, které je možné přidávat další informace.

public function deleteFormSubmitted($form)
{
	// ... požádáme model o smazání záznamu ...

	// předáme flash zprávu
	$this->flashMessage('Položka byla smazána.');

	$this->redirect(...); // a přesměrujeme
}

Šabloně jsou tyto zprávy k dispozici v proměnné $flashes jako anonymní objekty, které obsahují vlastnosti message (text zprávy), type (typ zprávy) a mohou obsahovat již zmíněné uživatelské informace. Vykreslíme je třeba takto:

{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Použití modelových tříd

Jak jsme si řekli, model je samostatná vrstva a je složen z hromady tříd, kde každá obstarává část logiky aplikace. Pokud bychom měli například model App\Articles, který se stará o načítání a ukládání článků, mohli bychom si o něj v presenteru říct pomocí Dependency Injection, za předpokladu že je registrovaný jako službaDI kontejneru.

class ArticlePresenter extends Presenter
{

	/** @var \App\Articles @inject */
	public $articles;

	public function renderShow($id)
	{
		$this->template->article = $this->articles->find($id);
	}

}

Co že se to právě stalo? Pojďme si to rozebrat. Jak jsme si již řekli, instance presenterů jsou vytvářeny pomocí PresenterFactory a tato třída po vytvoření presenteru koukne, jestli neobsahuje nějaké veřejné vlastnosti, které by měly annotaci @inject a pokud ano, najde službu co implementuje, nebo je instancí typu ve @var a tento do ní předá z DI kontejneru.

Díky tomu pak můžu tuto službu hned používat v metodách presenteru a nemusím se starat o její vytváření, protože mám jistotu, že mi bude vždy předána, pokud ji správně zaregistruji do DI kontejneru.

Injektovat lze pouze do public vlastností tříd.

Persistentní parametry

Krom klasických parametrů, které jsme používali nyní, existují i tzv. perzistentní parametry. Ty se liší v jediné avšak zásadní věci: přenášejí se automaticky. To znamená, že je nemusíme v odkazech explicitně uvádět, ale přesto se přenesou.

Pokud má vaše aplikace dvě jazykové mutace, bylo by neskutečně únavné v každém odkazu přenášet i aktuální jazyk. To není s Nette Framework potřeba. Prostě si parametr $lang označíme jako persistentní a to tímto způsobem:

class ProductPresenter extends Presenter
{
	/** @persistent */
	public $lang;

	...

Pokud aktuální hodnota parametru $lang bude 'en', tak do odkazu

<a n:href="Product:show $productId">detail produktu</a>

se automaticky doplní i lang => en. Paráda!

Samozřejmě můžeme lang uvést a jeho hodnotu změnit:

<a n:href="Product:show $productId, lang => cs">detail v češtině</a>

Zároveň v proměnné třídy $lang objektu ProductPresenter budeme mít hodnotu parametru k dispozici, můžeme k ní přistoupit přes $this->lang. Můžeme v definici třídy uvést i výchozí hodnotu persistentního parametru. Bude-li mít parametr tuto výchozí hodnotou, pak nebude přenášen v URL.

Persistentní proměnná musí být deklarovaná jako public.

Perzistence zohledňuje hierarchii tříd presenterů, tedy parametr definovaný v určitém presenteru je poté automaticky přenášen do každého presenteru z něj dědícího.

Presenter a komponenty

Bavíme-li se o presenterech, tak pod pojmem komponenty obvykle myslíme potomky třídy Control. Přesnější by tedy bylo používat termín „controls“ (tj. ovládací prvky), ale „kontrola“ má v češtině zcela jiný význam a spíš se ujaly „komponenty“.

Sám presenter Nette\Application\UI\Presenter je přitom potomkem třídy Control, takže je tu velká podobnost mezi komponentami a presenterem. Především však UI\Control (a tím pádem i UI\Presenter) je tzv. komponentový kontejner, což znamená, že do něj lze vkládat další komponenty. Podobně, jako třeba do komponenty formuláře vkládáme formulářové prvky (textové políčko, tlačítko, …). A stejně jako u formulářů lze k prvkům přistupovat přes hranaté závorky:

// připojíme komponentu do presenteru
$presenter['mymenu'] = new MenuControl;

// získáme komponentu z presenteru a vykreslíme
$presenter['mymenu']->render();

Připojením komponenty do presenteru (jejich svázáním) získáte možnost:

  • vytvářet v komponentě odkazy
  • používat signály
  • používat v komponentě perzistentní parametry

Pokud nic z toho nepotřebujeme, není potřeba komponentu s presenterem vázat.

Továrny na komponenty

Továrna na komponenty představuje elegantní způsob, jak komponenty vytvářet teprve ve chvíli, kdy jsou skutečně potřeba (lazy / on demand). Celé kouzlo spočívá v implementaci metody s názvem createComponent<Name>(), kde <Name> je název vytvářené komponenty, a která komponentu vytvoří a vrátí. Komponenta je následně připojena k presenteru. Metodě createComponent<Name> je předáván volitelný parametr s názvem komponenty, kterou vytváří.

class DefaultPresenter extends Nette\Application\UI\Presenter
{
	public function renderDefault()
	{
		$menu = $this['menu']; // přistoupíme ke komponentě
		// a pokud to bylo poprvé, zavolá se createComponentMenu()
		// ...
	}

	protected function createComponentMenu()
	{
		// vytvoříme a nakonfigurujeme komponentu
		$menu = new MenuControl;
		$menu->items = $this->item;
		// a vrátíme ji
		return $menu;
	}
}

Názvy komponent začínají vždy malým písmenem, přestože se v názvu továrny píší s velkým.

Díky tomu, že jsou všechny komponenty vytvářeny v samostatné metodě, získává kód na přehlednosti.

Továrny nikdy nevoláme přímo, zavolá se sama ve chvíli, kdy komponentu poprvé použijeme. Díky tomu je komponenta vytvořena ve správný okamžik a pouze v případě, když je skutečně potřeba. Pokud komponentu nepoužijeme (třeba při AJAXovém požadavku, kdy se přenáší jen část stránky, nebo při cachování šablony), nevytvoří se vůbec a ušetříme výkon serveru.

V šabloně je možné získat a vykreslit komponentu pomocí makra {control}. Není proto potřeba manuálně komponenty předávat do šablony.

<h2>Editační formulář</h2>

{control editForm}

Podrobnější informace o komponentách najdete na samostatné stránce.

Persistentní komponenty

Nejen parametry, ale také komponenty mohou být perzistentní. Jejich stav se pak přenáší při přechodu na jiný presenter podobně, jako v případě perzistentních parametrů. Perzistentní komponenty značíme touto anotací: (zde značíme komponenty calendar a menu):

/**
 * @persistent(calendar, menu)
 */
class DefaultPresenter extends Presenter
{
	// ...
}

Podkomponenty uvnitř těchto komponent není třeba nijak značit, jsou perzistentní samy o sobě.

Kde mohu získat komponenty?

Na stránce Doplňky, pluginy a komponenty můžete najít open-source komponenty, které sem umístili dobrovolníci z komunity okolo Nette Framework. Nette Foundation za ně neručí.


Související články na blogu