Formuláře
V této kapitole se seznámíme s formuláři a některými nástrahami, které k nim patří. Vytvoříme si jednoduchý formulář pro zakládání nových úkolů a další pro vytváření nových seznamů.
Formuláře z Nette lze používat samostatně, nezávisle na zbytku frameworku. Pokud se je ale rozhodneme používat v rámci Nette aplikaci ve spojení s presentery a plným komponentovým modelem, bude jejich používání ještě o něco jednodušší a hlavně radostnější.
Základní třídy sídlí ve jmenném prostoru Nette\Forms. Je
zde jak základní třída formuláře, tak veškeré formulářové komponenty
a třídy pro vykreslování. Pokud budeme formulář používat v Nette
aplikaci, budeme ještě potřebovat třídu
Nette\Application\UI\Form, která obsahuje napojení na presenter a
využívá signálů a událostí.
Nejlepší ale bude si vše ukázat v praxi.
Formulář pro zadání úkolu
Tento formulář bude zobrazen nad seznamem úkolů v dané kategorii. Jeho
definici tedy umístíme do TaskPresenteru. Bude obsahovat
políčko s textem a jeden selectbox na výběr uživatele, kterému má být
úkol přiřazen. Jeho definice bude vypadat následovně:
protected function createComponentTaskForm()
{
$form = new Form();
$form->addText('text', 'Úkol:', 40, 100)
->addRule(Form::FILLED, 'Je nutné zadat text úkolu.');
$form->addSelect('userId', 'Pro:', $this->context->createUsers()->fetchPairs('id', 'name'))
->setPrompt('- Vyberte -')
->addRule(Form::FILLED, 'Je nutné vybrat, komu je úkol přiřazen.');
$form->addSubmit('create', 'Vytvořit');
return $form;
}
Abychom mohli použít volání new Form(), musíme
na začátku souboru uvést deklaraci use
Nette\Application\UI\Form;.
Funkce createComponentTaskForm() je speciální tovární
funkcí na komponenty. Kdykoliv presenter požádáme o instanci komponenty
s názvem taskForm, tak se nejprve podívá, zda již takovou
komponentu nemá vytvořenou. Pokud ne, tak si zavolá právě tuto funkci.
Funkce buď jen komponentu vytvoří a vrátí ji jako svou návratovou hodnotu,
nebo jí rovnou připojí k presenteru.
Tovární metody na
komponenty jsou volány samotným presenterem. Neměly by být volány
přímo, ale je nutné, aby se k nim dostal presenter, proto musí být
protected.
Přímé připojení vypadá takto a je možné jej zavolat kdekoliv v presenteru, nejenom v továrničce
protected function createComponentTaskForm()
{
$form = new Form($this, 'taskForm');
//...
// return $form; by bylo zde zbytečné
}
Všechny třídy, které dědí od
Nette\ComponentModel\Component a nebyl jim změněn konstruktor,
mohou být takto připojeny k rodiči (v tomhle případě formulář
k presenteru). První argument je rodič (presenteru) a druhý název
komponenty taskForm. Továrnička dostává název komponenty
automaticky jako první argument, takže do kódu nemusíme název psát přímo
a snížíme tak riziko překlepu:
protected function createComponentTaskForm($name)
{
$form = new Form($this, $name);
// ...
}
Pokud formulář připojíte ihned v konstruktoru, budou si jednotlivé prvky při sestavování průběžně načítat data, která byla odeslána. Což se může hodit, pokud potřebujete podmínit vytvoření některých prvů, nebo validačních pravidel. Jinak jsou oba zápisy funkčně prakticky stejné.
Pojďme si nyní projít jednotlivé prvky formuláře:
$form->addText('text', 'Úkol:', 40, 100)
->addRule(Form::FILLED, 'Je nutné zadat text úkolu.');
Přidá nové textové políčko s názvem text a popiskou
Úkol:. Jeho velikost bude 40 znaků a maximální délka
100. Metoda addRule přidává validační pravidlo. Prvním
parametrem je konstanta, která udává typ pravidla. Pravidlo
Form::FILLED ověřuje, zda bylo políčko vyplněno. Druhý
parametr je nepovinný a definuje hlášku, která se uživateli zobrazí
v případě, že pravidlo nebylo splněno. Metoda má ještě třetí
nepovinný parametr a tím jsou parametry validace, například v případě
pravidla Form::MIN_LENGTH udává tento parametr minimální délku
řetězce, který uživatel musí zadat.
Další validační pravidla jsou uvedena v dokumentaci k formulářům. Obecná validační pravidla lze aplikovat na všechny prvky, dále pak má každý prvek vlastní sadu pravidel, která na něj lze aplikovat. Podívejte se například na pravidla k textovému políčku.
$form->addSelect('userId', 'Pro:', $this->context->createUsers()->fetchPairs('id', 'name'))
->setPrompt('- Vyberte -')
->addRule(Form::FILLED, 'Je nutné vybrat, komu je úkol přiřazen.');
Tento kód přidává do formuláře selectbox. První dva parametry jsou
stejné, jak v předchozím případě. Třetí argument je asociativní pole
ve tvaru hodnota → popis volby. Takové pole
můžeme získat metodou fetchPairs() zavolanou nad objektem
tabulky. fetchPairs('id', 'name') použije jako klíč v poli ID
uživatele a jako popisku volby jeho jméno. Metoda setPrompt()
přidá na začátek volbu s danou popiskou a prázdnou hodnotou. Taková volba
pak nám umožňuje oddělit situaci, kdy uživatel prvek skutečně nevyplnil,
a kdy jen ponechal v prvku výchozí hodnotu. V kombinaci s validačním
pravidlem Form::FILLED pak způsobí, že uživatel musí vždy
nějaký prvek vybrat.
Posledním prvkem je odesílací tlačítko:
$form->addSubmit('create', 'Vytvořit');
Parametry jsou opět stejné. Popiska tlačítka tak bude
Vytvořit.
Nyní máme tento jednoduchý formulář téměř kompletní. Zbývá nám
jej jen vykreslit. Přesuneme se tedy do šablony
Task/default.latte a nad tabulku s výpisem prvků přidáme:
<fieldset>
<legend>Přidat úkol</legend>
{control taskForm}
</fieldset>
Povšimněte si nového makra {control}. To zajistí vykreslení
komponenty se zadaným názvem. V našem případě presenter nejprve zjistí,
zda již existuje komponenta s názvem taskForm. Pokud neexistuje,
zavolá si metodu createComponentTaskForm a komponentu si
vytvoří. Pak zajistí vykreslení pomocí metody render(), kterou
má formulář definovanou. Blíže si ji představíme v další kapitole.
Na stránce bychom teď měli vidět následující výsledek:

Zkusíme si cvičně přidat úkol. Vyplníme text úkolu, vybereme komu má
být přiřazen a potvrdíme… Ouha, ale nic se nestalo! To proto, že
prozatím nemáme napsanou obsluhu odeslaného formuláře. K tomu slouží
událost onSuccess, která se vykoná po úspěšném odeslání
formuláře, což také znamená, že pokud formulář obsahuje validační
pravidla, musí být správně vyplněn. Za přidání tlačítka pro odeslání
formuláře tedy přidáme následující řádek:
$form->onSuccess[] = callback($this, 'taskFormSubmitted');
Ten nastavuje formuláři, že po jeho úspěšném odeslání se má vykonat
metoda taskFormSubmitted z objektu $this, tedy
z aktuálního presenteru. To ovšem znamená, že jí musíme vytvořit:
public function taskFormSubmitted(Form $form)
{
$this->context->createTasks()->insert(array(
'text' => $form->values->text,
'user_id' => $form->values->userId,
'created' => new DateTime(),
'tasklist_id' => $this->taskList->id
));
$this->flashMessage('Úkol přidán.', 'success');
$this->redirect('this');
}
Narozdíl od tovární metody je zpracování události voláno
samotnou komponentou, nikoliv presenterem. Proto musí být
public.
Tato metoda dostane jako jediný parametr instanci formuláře, který byl
odeslán. Do databáze vloží pomocí metody insert() data nového
úkolu. Data jsou předána jako asociativní pole, kde jako klíč je uveden
název sloupečku. Povšimněte si, že pro naplnění sloupce
created jsme použili instanci DateTime(). Nette samo
před vložením čas převede na formát používaný databází. Sloupeček
done uvedený není, neboť má výchozí hodnotu 0.
Metoda flashMessage() vypíše uživateli tzv. flash zprávu. Jedná se
o jednorázové oznámení o výsledku stavu akce. O jejich vykreslení se
pak staráme v šabloně @layout.latte. Druhý parametr udává
třídu zprávičky, v tomto případě použijeme success.
Hlášení pak díky definici stylů z minulé kapitoly bude mít krásnou
uklidňující zelenou barvu.
Metoda redirect() pak konečně provede přesměrování. Má
stejné parametry, jako jsme uváděli v šabloně makru {link}.
Klíčové slovo this místo dvojice Presenter:action
nás přesměruje na aktuální stránku se stejnými parametry. Toto
přesměrování je velmi důležité. Pokud bychom ho neprovedli, uživateli by
se uložil odeslaný POST formulář do historie prohlížeče.
Pokud by se pak na tuto stránku vrátil, formulář by se odeslal znovu a tím
pádem by se záznam v databázi vytvořil ještě jednou. Podobně by se pak
chovalo obnovení stránky.
Nyní již vytváření úkolů bude plně funkční. Vzhled formuláře však není ideální. Nette vykresluje formuláře do tabulek, takže se nám do formuláře trochu vmíchal styl seznamu úkolů. Pojďme si to tedy napravit.
Vlastní vykreslení formuláře
Pokud chceme jednotlivé prvky formuláře vykreslit ručně, můžeme tak
učinit pomocí latte maker {form}, {input} a
párového {label /}. Jejich názvy mluví za vše, proto si je
ukážeme v praxi:
{form taskForm}
<div class="task-form">
{control $form errors}
{label text /} {input text size => 30, autofocus => true} {label userId /} {input userId} {input create}
</div>
{/form}
Počáteční {form taskForm} říká, že budeme vykreslovat
formulář s názvem taskForm. Uvnitř je jeden vnořený
<div> a v něm na jedné řádce všechny prvky
formuláře.
Ještě před nimi je však nutné zajistit případné vykreslení chyb ve
vyplněném formuláři. To zajistíme makrem {control $form
errors}. Před odesláním se sice provádí kontrola JavaScriptem
u klienta, pokud jej ale bude mít uživatel vypnutý, formulář se odešle a
je nutné mu chyby zobrazit takto. Některé chyby navíc není možné
u klienta ověřit, proto na výpis chyb nesmíme zapomenout.
Pak hned následují jednotlivé prvky. První {label text /}
vykreslí popisku pro prvek text. Jak již bylo řečeno, makro
{label /} je párové. Buď můžeme do jeho obsahu napsat popisku
přímo v šabloně ({label text}Text:{/label}), nebo makro
ukončit podobně jako HTML tag. V takovém případě se použije popiska
zadaná při definici formuláře.
Makro {input text} vykreslí samotný formulářový prvek.
Volitelně mu lze přidat seznam atributů, které chceme HTML elementu
přiřadit. Zde nastavujeme size na 30 a povolujeme
autofocus, který je podporován jen v novějších
prohlížečích.
Výsledný formulář bude nyní vypadat takto:

Nyní vytvoříme další formulář, tentokrát pro vytvoření seznamu úkolů.
Formulář na vytvoření seznamu úkolů
Tento formulář by bylo vhodné umístit do levého sloupce, ihned pod
seznam úkolů. Bude zobrazen na každé stránce, proto jej budeme definovat v
BasePresenteru. Protože tento formulář je velmi podobný, rovnou
uveďme kód. BasePresenter:
protected function createComponentNewTasklistForm()
{
$form = new Form();
$form->addText('title', 'Název:', 15, 50)
->addRule(Form::FILLED, 'Musíte zadat název seznamu úkolů.');
$form->addSubmit('create', 'Vytvořit');
$form->onSuccess[] = callback($this, 'newTasklistFormSubmitted');
return $form;
}
public function newTasklistFormSubmitted(Form $form)
{
$tasklist = $this->context->createTasklists()->insert(array(
'title' => $form->values->title
));
$this->flashMessage('Seznam úkolů založen.', 'success');
$this->redirect('Task:default', $tasklist->id);
}
Povšimněte si přesměrování. Metoda insert() vrací
záznam, který byl vložen do databáze. Můžeme tak tedy snadno provést
přesměrování na nově vytvořený seznam úkolů.
V šabloně @layout.latte upravíme postranní panel:
<div id="sidebar">
<div class="task-lists">
<ul>
<li n:foreach="$taskLists as $list"><a n:href="Task: $list->id">{$list->title}</a></li>
</ul>
</div>
<fieldset>
<legend>Nový seznam</legend>
{form newTasklistForm}
<div class="new-tasklist-form">
{control $form errors}
{input title}
{input create}
</div>
{/form}
</fieldset>
</div>
Závěr
V této kapitole jsme vytvořili formuláře jak pro vkládání úkolů, tak pro zakládání nových seznamů. V další kapitole si napíšeme vlastní komponentu.
Zdrojové kódy aplikace v této fázi naleznete na githubu v tagu 04_forms, případně si je také můžete stáhnout: 04_forms.zip.