Formuláře
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.
Abychom mohli vybrat, komu je úkol přiřazen, budeme potřebovat uživatele. Metodu startup() máme
v TaskPresenteru už definovanou, takže si ji rozšíříme o získání modelu pro uživatele.
// class TaskPresenter
/** @var Todo\UserRepository */
private $userRepository;
protected function startup(): void
{
parent::startup();
$this->listRepository = $this->context->listRepository;
$this->userRepository = $this->context->userRepository; // získáme model pro práci s uživateli
}
Definice formuláře bude vypadat následovně:
protected function createComponentTaskForm(): Form
{
$userPairs = $this->userRepository->findAll()->fetchPairs('id', 'name');
$form = new Form();
$form->addText('text', 'Úkol:', 40, 100)
->addRule(Form::FILLED, 'Je nutné zadat text úkolu.');
$form->addSelect('userId', 'Pro:', $userPairs)
->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
{
$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árna 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(string $name): Form
{
$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.
$prompt = Html::el('option')->setText("- Vyberte -")->class('prompt');
$form->addSelect('userId', 'Pro:', $userPairs)
->setPrompt($prompt) // je možné předat text i prvek HTML
->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ší části.
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
ještě jeden řádek:
$form->addSubmit('create', 'Vytvořit');
$form->onSuccess[] = [$this, 'taskFormSubmitted']; // naše událost pro zpracování formuláře
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(Nette\Application\UI\Form $form, stdClass $values): void
{
$this->taskRepository->createTask($this->list->id, $form->values->text, $form->values->userId);
$this->flashMessage('Úkol přidán.', 'success');
$this->redirect('this');
}
Budeme v ní potřebovat zbývající službu taskRepository, takže si ji předáme do presenteru.
/** @var Todo\TaskRepository */
private $taskRepository;
protected function startup(): void
{
parent::startup();
$this->listRepository = $this->context->listRepository;
$this->userRepository = $this->context->userRepository;
$this->taskRepository = $this->context->taskRepository; // předání potřebné služby
}
A vytvoříme si v ní metodu, která nám zpracuje vložení záznamu
// class TaskRepository
public function createTask($listId, $task, $assignedUser)
{
return $this->getTable()->insert([
'text' => $task,
'user_id' => $assignedUser,
'created' => new \DateTime(),
'list_id' => $listId
]);
}
Nyní ji můžeme ve zpracování formuláře zavolat.
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ávy, v tomto případě použijeme success. Hlášení pak díky definici stylů z minulé části 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 createComponentNewListForm(): Form
{
$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[] = [$this, 'newListFormSubmitted'];
return $form;
}
public function newListFormSubmitted(Nette\Application\UI\Form $form, stdClass $values): void
{
$list = $this->listRepository->createList($form->values->title);
$this->flashMessage('Seznam úkolů založen.', 'success');
$this->redirect('Task:default', $list->id);
}
A ještě si implementujeme metodu createList ve třídě ListRepository
public function createList(string $title)
{
return $this->getTable()->insert([
'title' => $title
]);
}
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="$lists as $list"><a n:href="Task: $list->id">{$list->title}</a></li>
</ul>
</div>
<fieldset>
<legend>Nový seznam</legend>
{form newListForm}
<div class="new-list-form">
{control $form errors}
{input title}
{input create}
</div>
{/form}
</fieldset>
</div>
Vytvořili jsme formuláře jak pro vkládání úkolů, tak pro zakládání nových seznamů. Nyní si napíšeme vlastní komponentu.
Zdrojové kódy aplikace v této fázi naleznete na GitHubu.