Formuláře
Nette\Forms výrazně usnadňují vytváření a zpracování webových formulářů ve vašich aplikacích. Co všechno umí?
- validovat odeslaná data na straně serveru i JavaScriptem
- poskytují zabezpečení proti zranitelnostem
- zvládají několik režimů vykreslování
- vícejazyčnost
K čemu vůbec nasazovat framework na jednoduchý formulář? Vyhneme se tak celé řadě rutinních úkolů, jako je třeba psaní dvojí validace (na straně serveru a JavaScriptu), minimalizujeme pravděpodobnost vzniku chyb a bezpečnostních děr.
Nette Framework klade velký důraz na bezpečnost aplikací a proto úzkostlivě dbá i na dobré zabezpečení formulářů. Dělá to zcela transparentně a nevyžaduje manuálně nic nastavovat. Ochrání vaše aplikace před útokem Cross Site Scripting (XSS) i Cross-Site Request Forgery (CSRF), odfiltruje ze vstupů kontrolní znaky, ověří validitu UTF-8 kódování nebo jestli nejsou položky vybrané v select boxech podvržené atd. To zní zajímavě, pojďme to vyzkoušet!
První formulář
Vytvoříme si jednoduchý registrační formulář:
require 'Nette/loader.php';
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('send', 'Registrovat');
echo $form; // vykreslí formulář
Upozornění: skripty musí být uloženy v UTF-8.
V prohlížeči se vykreslí takto:

Vytvořili jsme formulář, který se metodou POST odešle na stejnou
stránku, na jaké se nachází. Což lze případně snadno změnit. Takto
bychom mohli formulář odeslat kupříkladu metodou GET na
adresu /submit.php
$form = new Form;
$form->setAction('/submit.php');
$form->setMethod('GET');
...
U každé metody addPrvek() představuje
první parametr identifikátor prvku. K jednotlivým prvkům formuláře
$form lze poté přistupovat pomocí hranatých závorek, podobně
jako k prvkům pole. Takže třeba pod $form['name'] se skrývá
objekt Nette\Forms\Controls\TextInput představující první
políčko formuláře.
Vykreslený formulář splňuje základní pravidlo přístupnosti –
všechny popisky jsou zapsány jako <label> a provázané
s příslušným formulářovým prvkem. Při kliknutí na popisku se kurzor
automaticky objeví ve formulářovém políčku.
Teď formulář oživíme. Dotazem na $form->isSuccess()
zjistíme, zda byl formulář odeslán a zda byl vyplněn korektně. Pokud bude
formulář správně vyplněn, data vypíšeme do okna prohlížeče. Za
definici formuláře tedy vložíme kód:
if ($form->isSuccess()) {
echo 'Formulář byl správně vyplněn a odeslán';
$values = $form->getValues();
dump($values);
}
Po odeslání a zpracování formuláře je vhodné přesměrovat na další stránku. Zabrání se tak nechtěnému opětovnému odeslání formuláře tlačítkem Obnovit nebo Zpět.
V presenterech se místo třídy Nette\Forms\Form
používá od ní odvozená třída Nette\Application\UI\Form,
kde se formulář oživuje trošku jiným způsobem. Více v části formuláře v presenterech.
Data získaná metodou getValues() neobsahují hodnoty
formulářových tlačítek, tak je lze obvykle rovnou použít pro další
zpracování (například vložení do databáze). Zároveň si můžete
všimnout, že z textových políček jsou automaticky odstraněny levo-
i pravostranné mezery. Schválně si zkuste do políčka napsat své jméno a
za něj několik mezer – po odeslání budou mezery ořezané.
Zmínili jsme se o validaci, ale formulář zatím žádná validační
pravidla nemá. Pojďme to napravit. Jméno bude povinné, proto je označíme
metodou setRequired(), jejíž volitelný argument je text chybové
hlášky, která se zobrazí, pokud uživatel jméno nevyplní:
$form->addText('name', 'Jméno:')
->setRequired('Zadejte prosím jméno');
Zkuste si odeslat formulář bez vyplněného jména a uvidíte, že se
zobrazí chybová hláška a server vám jej bude nabízet do té doby, dokud
jej nevyplníte v souladu s validačními pravidly. Zbývá ještě zprovoznit
validaci na straně JavaScriptu, což je nesmírně jednoduché – stačí
zalinkovat soubor netteForms.js, který najdete v distribuci
v adresáři client-side/forms. Zkopírujeme jej do složky
s formulářem a přidejte kód:
<script src="netteForms.js"></script>
Nette Framework povinným prvkům nastaví CSS třídu required.
Zkusme přidat stylopis
<style>
.required label { color: maroon }
</style>
a popiska „Jméno“ bude červená.
Označením povinných prvků validování pochopitelně nekončí. Přidáme
další validační pravidla metodou addRule(), jejíž první
argument říká, co chceme ověřovat, a druhý argument je opět text
hlášky, která se zobrazí, pokud hodnota validací neprojde. Můžeme si
vytvářet i vlastní validační pravidla, zatím si však vystačíme
s předdefinovanými.
Formulář rozšíříme o nové políčko „věk“ s podmínkou, že
musí být číslo (Form::INTEGER) a navíc v povoleném rozsahu
(Form::RANGE). Zde využijeme třetí parametr metody
addRule(), kterým předáme validátoru požadovaný rozsah:
$form->addText('age', 'Věk:')
->addRule(Form::INTEGER, 'Věk musí být číslo')
->addRule(Form::RANGE, 'Věk musí být od 18 do 120', array(18, 120));
Zde vzniká prostor pro drobný refactoring. V chybové hlášce a ve třetím parametru jsou čísla uvedená duplicitně, což není ideální. Pokud bychom tvořili vícejazyčné formuláře a hláška obsahující čísla by se musela přeložit do více jazyků, ztížila by se pozdější změna hodnot. Z toho důvodu je možné použít zástupné znaky v tomto formátu:
->addRule(Form::RANGE, 'Věk musí být od %d do %d let', array(18, 120));
Nette Framework podporuje HTML5 včetně nových formulářových prvků. Díky tomu můžeme políčko pro zadání věku označit jako číselné:
$form->addText('age', 'Věk:')
->setType('number')
...
V nejpokročilejších prohlížečích, jako je Chrome, Safari nebo Opera, se zobrazí šipečky pro snadnější změnu hodnoty, iPhone zobrazí optimalizovanou klávesnici s číslicemi.

Vraťme se k prvku password, který taktéž učiníme
povinným a ještě ověříme minimální délku hesla
(Form::MIN_LENGTH), opět s využitím zástupného znaku:
$form->addPassword('password', 'Heslo:')
->setRequired('Zvolte si heslo')
->addRule(Form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaky', 3);
Přidáme do formuláře ještě políčko passwordVerify, kde
uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních
pravidel zkontrolujeme, zda jsou obě hesla stejná (Form::EQUAL).
Všimněte si dynamické odvolávky na první heslo pomocí hranatých
závorek:
$form->addPassword('passwordVerify', 'Heslo pro kontrolu:')
->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu')
->addRule(Form::EQUAL, 'Hesla se neshodují', $form['password']);
Pokud by formulář nesloužil k registraci nových uživatelů, ale pro editaci záznamů, hodilo by se na začátku nastavit prvkům výchozí hodnoty.
Tímto máme hotový plně funkční formulář, který disponuje validací na straně klienta (tj. JavaScriptovou validací) i validací na straně serveru. Automaticky ošetřuje magic quotes, ověřuje, zda útočník neposílá nevalidní UTF-8 řetězce apod. Na tyto věci nemusíme myslet.
Příklady si můžete stáhnout. Zkuste si
do něj přidat i další prvky popsané níže. Inspiraci najdete také v distribuci
v adresáři examples/Forms.
Formulářové prvky
Přehled standardních formulářových prvků.
addText($name, $label = NULL, $cols = NULL, $maxLength = NULL)
Přidá jednořádkové textové políčko (třída TextInput). Automaticky ořezává levo- a pravostranné mezery nebo případné odřádkování. Kromě standardních validačních pravidel lze použít navíc i tato:
Form::MIN_LENGTH |
minimální délka |
Form::MAX_LENGTH |
maximální délka |
Form::LENGTH |
právě tato délka |
Form::EMAIL |
je hodnota platná e-mailová adresa? |
Form::URL |
je hodnota absolutní URL? |
Form::PATTERN |
test oproti regulárnímu výrazu |
Form::INTEGER |
je hodnota celočíselná? |
Form::FLOAT |
je hodnota číslo? |
Form::RANGE |
je hodnota v daném rozsahu? |
$form->addText('zip', 'PSČ:')
->addRule(Form::PATTERN, 'PSČ musí mít 5 číslic', '([0-9]\s*){5}');
addPassword($name, $label = NULL, $cols = NULL, $maxLength = NULL)
Přidá políčko pro zadání hesla (třída TextInput). Automaticky ořezává levo- a pravostranné mezery nebo případné odřádkování. Při znovu-zobrazení formuláře bude políčko prázdné. Lze použít stejná validační pravidla jako pro addText.
$form->addPassword('password', 'Heslo:')
->addRule(Form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaky', 3);
->addRule(Form::PATTERN, 'Musí obsahovat číslici', '.*[0-9].*');
addTextArea($name, $label = NULL)
Přidá pole pro zadání víceřádkového textu (třída TextArea). Lze použít stejná validační pravidla jako pro addText. Na rozdíl od jednořádkového vstupního políčka k žádnému ořezávání mezer nedochází.
$form->addTextArea('note', 'Poznámka:')
->addRule(Form::MAX_LENGTH, 'Poznámka je příliš dlouhá', 10000);
addUpload($name, $label = NULL)
Přidá políčko pro upload souboru (třída UploadControl). Kromě standardních validačních pravidel lze použít navíc i tato:
Form::MAX_FILE_SIZE |
ověřuje maximální velikost souboru |
Form::MIME_TYPE |
ověření MIME type |
Form::IMAGE |
ověření, že jde o obrázek JPEG, PNG nebo GIF |
$form->addUpload('avatar', 'Avatar:')
->addRule(Form::IMAGE, 'Avatar musí být JPEG, PNG nebo GIF.')
->addRule(Form::MAX_FILE_SIZE, 'Maximální velikost souboru je 64 kB.', 64 * 1024 /* v bytech */);
addHidden($name, $default = NULL)
Přidá skryté pole (třída HiddenField).
$form->addHidden('userid');
addCheckbox($name, $caption = NULL)
Přidá zaškrtávací políčko (třída Checkbox). Políčko vrací hodnotu buď TRUE nebo FALSE, podle toho, zda je zaškrtnuté či nikoliv.
$form->addCheckbox('agree', 'Souhlasím s podmínkami')
->addRule(Form::EQUAL, 'Je potřeba souhlasit s podmínkami', TRUE);
addRadioList($name, $label = NULL, array $items = NULL)
Přidá přepínací tlačítka (třída RadioList). Pole nabízených hodnot předáme jako třetí parametr.
$sex = array(
'm' => 'muž',
'f' => 'žena',
);
$form->addRadioList('gender', 'Pohlaví:', $sex);
// pro vypsání možností do 1 řádku
$form->addRadioList('gender', 'Pohlaví:', $sex)
->getSeparatorPrototype()->setName(NULL);
addSelect($name, $label = NULL, array $items = NULL)
Přidá select box (třída SelectBox).
U select boxů má často první položka speciální význam, slouží jako
výzva k akci. K přidání takové položky slouží metoda
setPrompt(). Pole nabízených hodnot předáme jako třetí
parametr. Pole může být i dvourozměrné:

$countries = array(
'Europe' => array(
'CZ' => 'Česká Republika',
'SK' => 'Slovensko',
'GB' => 'Velká Británie',
),
'CA' => 'Kanada',
'US' => 'USA',
'?' => 'jiná',
);
$form->addSelect('country', 'Země:', $countries)
->setPrompt('Zvolte zemi');
Prvky je rovněž možné přidat pomocí metody setItems(). Pokud chceme místo klíčů položek získat přímo jejich hodnoty, můžeme toho docílit druhým argumentem:
$form->addSelect('country', 'Země:')
->setItems($countries, FALSE);
addMultiSelect($name, $label = NULL, array $items = NULL)
Přidá select box pro výběr více položek (třída MultiSelectBox).
$form->addMultiSelect('options', 'Možnosti:', $options);
addSubmit($name, $caption = NULL)
Přidá odesílací tlačítko (třída SubmitButton).
$form->addSubmit('submit', 'Odeslat');
Pokud stisknutí tlačítka nemá provádět validaci (vhodné pro tlačítka
Cancel nebo Preview), vypneme ji metodou
setValidationScope(FALSE).
addButton($name, $caption)
Přidá tlačítko (třída Button), které nemá odesílací funkci. Lze ho tedy využít na nějakou jinou funkci, např. zavolání js funkce při kliknutí.
$form->addButton('add', 'Zvýšit plat')
->setAttribute("onclick()","raisePayment");
addImage($name, $alt = NULL)
Přidá odesílací tlačítko v podobě obrázku (třída ImageButton).
$form->addImage('submit', '/path/to/image');
addContainer($name)
Přidá pod-formulář (třída Container), do
které lze přidávat další prvky stejným způsobem, jako je přidáváme do
formuláře. Fungují i metody setDefaults() nebo
getValues().
$sub1 = $form->addContainer('first');
$sub1->addText('name', 'Your name:');
$sub1->addText('email', 'Email:');
$sub2 = $form->addContainer('second');
$sub2->addText('name', 'Your name:');
$sub2->addText('email', 'Email:');
Validace
Na již zmíněné prvky lze použít tyto validační pravidla:
Form::FILLED |
je prvek vyplněn? |
Form::EQUAL |
je hodnota rovna uvedené? |
Form::IS_IN |
testuje, zda hodnota spadá do výčtu |
Form::VALID |
je prvek vyplněn správně? |
Všem validačním pravidlům můžeme přidat vlastní chybovou hlášku, nebo se použije hláška výchozí. U vícejazyčných formulářů se hlášky automaticky překládají.
V textu chybových hlášek lze používat i speciální zástupné řetězce:
| %label | nahradí se textem popisky |
| %name | nahradí se identifikátorem prvku |
| %value | nahradí se zadanou hodnou |
Kromě pravidel lze přidávat také dotazovací podmínky. Ty se zapisují
podobně jako pravidla, jen místo addRule() použijeme metodu
addCondition() a pochopitelně neuvádíme žádnou chybovou
zprávu (podmínka se jen ptá):
$form->addPassword('password', 'Heslo:')
// pokud není heslo delší než 5 znaků
->addCondition(Form::MAX_LENGTH, 5)
// pak bude muset obsahovat číslici
->addRule(Form::PATTERN, 'Musí obsahovat číslici', '.*[0-9].*');
Podmínku je možné vázat i na jiný prvek, než ten aktuální. Stačí
addCondition() nahradit za addConditionOn() a jako
první parametr uvést odvolávku na jiný prvek. V tomto případě se bude
e-mail vyžadovat tehdy, zaškrtne-li se checkbox (tj. jeho logická hodnota
bude TRUE):
$form->addCheckbox('newsletters', 'zasílejte mi newslettery');
$form->addText('email', 'E-mail:')
// pokud je checkbox zaškrtnut
->addConditionOn($form['newsletters'], Form::EQUAL, TRUE)
// pak vyžaduj e-mail
->addRule(Form::FILLED, 'Zadejte e-mailovou adresu');
Pravidla a podmínky je možné negovat znakem ~ (vlnovka), tj.
addRule(~Form::FILLED, ...). Také lze z podmínek vytvářet
komplexní struktury za pomoci metod elseCondition() a
endCondition().
Jak vidíte, jazyk pro formulování podmínek a pravidel je velice silný. Všechny konstrukce přitom fungují jak na straně serveru, tak i na straně JavaScriptu.
Můžeme si také přidat vlastní validátory. Metody addRule()
a addCondition() totiž jako název pravidla akceptují
callback:
// uživatelský validátor: testuje, zda je hodnota dělitelná argumentem
function divisibilityValidator($item, $arg)
{
return $item->value % $arg === 0;
}
$form->addText('number', 'Číslo:')
->addRule('divisibilityValidator', 'Číslo musí být dělitelné %d.', 8);
Validace celého formuláře
Dosud jsme mluvili pouze o validaci na úrovni jednotlivých políček. Můžete ale také potřebovat validovat formulář jako celek, např. kombinaci hodnot několika políček.
Takových validačních funkcí můžete definovat libovolné množství a k formuláři je navážete přes onValidate:
protected function createComponentSignInForm()
{
$form = new SignInForm();
...
$form->onValidate[] = callback($this, 'validateSignInForm');
return $form;
}
public function validateSignInForm($form)
{
$values = $form->getValues();
if (...) { // validační podmínka
$form->addError('Tato kombinace není možná.');
}
}
Vlastní chyby
V mnoha případech se o chybě dozvíme až ve chvíli, kdy zpracováváme
platný formulář, například zapisujeme novou položku do databáze a
narazíme na duplicitu klíčů. V takovém případě chybu zpětně předáme
do formuláře metodou addError(). Tu lze volat buď na
konkrétním prvku, nebo přímo na formuláři:
try {
$values = $form->getValues();
$this->user->login($values->username, $values->password);
$this->redirect('Homepage:');
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
JavaScript
Validační pravidla se na stranu JavaScriptu přenášejí v HTML atributes
data-nette-rules, které obsahují JavaScriptový objekt
popisující jednotlivá pravidla nebo podmínky. Samotnou validaci pak
provádí skript, který odchytí událost submit, projde
jednotlivé prvky a vykoná příslušnou validaci. Výchozí implementací je
soubor netteForms.js, který najdete v distribuci v adresáři
client-side/forms. Stačí jej tedy do stránky zalinkovat přes
<script src="netteForms.js"></script>.
Vlastní validační pravidla přidáme rozšířením objektu
Nette.validators:
<script>
Nette.validators.divisibilityValidator = function(elem, args, val) {
return val % args === 0;
};
</script>
Zpracování hodnot
K hodnotám formuláře se dostaneme pomocí metody getValues(), která nám vrací objekt typu ArrayHash. Lze ale získat i data jako pole.
/* object arrayHash */
$values = $form->getValues();
/* array */
$values = $form->getValues(TRUE);
Pokud prvek ve formuláři je, ale jeho hodnotu pomocí výše uvedného
způsobu nelze získat – typicky hodnota selectu při použití
ajax snippetu -, lze využít metodu getHttpData()
vracející pole.
$values = $form->getHttpData();
Další rozšíření prvků
Formulářové prvky můžeme kromě podmínek a pravidel rozšířit
o popisek, upravit class apod.
Pozor na pořadí podmínek, pravidel a užití těchto rozšíření.
Nastavení třídy nebo js:
$form->addText('number', 'Číslo:')
->setAttribute('class', 'bigNumbers');
$form->addSelect('rank', 'Řazení dle:', array('ceny', 'názvu'))
->setAttribute('onchange', 'submit()'); // při změně odeslat
// chceme-li to samé udělat pro celý $form
$form->getElementPrototype()->id = "myForm";
$form->getElementPrototype()->target = "_top";
Nastavení typu (např. pro HTML5):
$form->addText('email', 'Váš email:')
->setAttribute('type', 'email')
->setAttribute('placeholder', 'napište email');
Nastavení popisku (defaultně se vypisuje za polem):
$form->addText('phone', 'Číslo:')
->setOption('description', 'Toto číslo zůstane skryté');
Pokud chceme umístit HTML obsah, využijeme třídy Html
use Nette\Utils\Html;
$form->addText('phone', 'Číslo:')
->setOption('description', Html::el("p")
->setHtml("Toto číslo zůstane skryté. <a href='...'>Podmínky uchovávání Vašeho čísla</a>")
);
Html prvek lze využít i místo labelu,
$form->addCheckbox('conditions', $readConditions);
Formuláře v presenterech
Formulář přidáme do presenteru pomocí tzv. továrničky:
protected function createComponentSignInForm()
{
$form = new Nette\Application\UI\Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('login', 'Přihlásit se');
$form->onSuccess[] = callback($this, 'signInFormSubmitted');
return $form;
}
public function signInFormSubmitted($form)
{
// volá se po odeslání formuláře
}
Pokud má formulář více než jedno tlačítko, mezi kterými chceme
rozlišovat, je vhodnější nastavit handler na událost onClick
tlačítka. Ten se volá před handlerem události onSuccess:
$form->addSubmit('login', 'Přihlásit se')
->onClick[] = callback($this, 'signInFormSubmitted');
Když se formulář odešle tlačítkem enter, za odesílací tlačítko se považuje to první.
Handlery událostí onSuccess a onClick se volají
pouze v případě, že je odeslání validní. Uvnitř obslužných
metod tedy nemusíme validitu ověřovat. Formulář má ještě událost
onSubmit, která se volá vždy nezávisle na validitě.
Použití stejného formuláře ve více presenterech
Pokud potřebujete jeden formulář použít ve více presenterech, máte dvě možnosti:
- vložit do hierarchie presenterů jejich společného předka a továrničku definovat tam
- nebo definovat formulář v samostatné třídě jako komponentu a v jednotlivých továrničkách vytvářet jeho instance.
Vhodné umístění pro takovou třídu je
app/forms/SignInForm.php
use Nette\Application\UI,
Nette\ComponentModel\IContainer;
class SignInForm extends UI\Form
{
public function __construct(IContainer $parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
$this->addText('name', 'Jméno:');
// ...
$this->addSubmit('login', 'Přihlásit se');
}
}
V továrničce každého presenteru, který náš formulář zobrazuje, ji pak použijeme takto:
protected function createComponentSignInForm($name)
{
$form = new SignInForm();
$form['login']->caption = 'Pokračovat'; // můžeme také formulář pozměnit
$form->onSuccess[] = callback($this, 'signInFormSubmitted');
return $form;
}
Odeslaný formulář ale můžeme také zpracovávat na jediném
místě. Do definice formuláře přesuneme volání událostí i s metodou
signInFormSubmitted a přejmenujeme ji například na
submitted.
Výchozí hodnoty
Nastavit výchozí hodnoty lze dvěma způsoby. Metodou
setDefaults() nad celým formulářem nebo kontejnerem:
$form->addText('name', 'Jméno');
$form->addText('age', 'Věk');
$form->setDefaults(array(
'name' => 'John',
'age' => '33'
));
nebo metodou setDefaultValue() nad prvkem:
$form->addText('email', 'E-mail')
->setDefaultValue('user@example.com');
U SelectBoxu nebo RadioListu zadáme jako výchozí hodnotu klíč z předaného pole hodnot:
$form->addSelect('country', 'Country', array(
'cz' => 'Česká republika',
'sk' => 'Slovensko',
));
$form['country']->setDefaultValue('sk');
U CheckBoxu zvolíme výchozí stav ‚zaškrtnuto‘ pomocí
setDefaultValue(true).
Vzhled formulářů
Vzhled formulářů může být velmi různorodý. V praxi můžeme narazit
na dva extrémy. Na jedné straně stojí potřeba v aplikaci vykreslovat řadu
formulářů, které jsou si vizuálně podobné jako vejce vejci, a oceníme
snadné vykreslení pomocí echo $form. Jde obvykle o případ
administračních rozhraní.
Na druhé straně tu jsou rozmanité formuláře, kde platí: co kus, to originál. Jejich podobu nejlépe popíšeme jazykem HTML. A samozřejmě kromě obou zmíněných extrémů narazíme na spoustu formulářů, které se pohybují někde mezi.
Výchozí DefaultFormRenderer
Automatické vykreslení formuláře obstarává tzv. renderer. Ten lze
nastavit metodou setRenderer. Předá se mu řízení při
zavolání metody $form->render() nebo echo $form.
Pokud nenastavíme vlastní renderer, bude použit výchozí vykreslovač Nette\Forms\Rendering\DefaultFormRenderer.
Stačí tedy napsat:
echo $form;
a formulář je na světě. Prvky formuláře se vykreslí do HTML tabulky. Výstup vypadá takto:
<table>
<tr class="required">
<th><label class="required" for="frm-name">Jméno:</label></th>
<td><input type="text" class="text" name="name" id="frm-name" value="" /></td>
</tr>
<tr class="required">
<th><label class="required" for="frm-age">Věk:</label></th>
<td><input type="text" class="text" name="age" id="frm-age" value="" /></td>
</tr>
<tr>
<th><label>Pohlaví:</label></th>
...
Hezky naformátované, viďte? :-)
Zda použít nebo nepoužít pro kostru formuláře tabulku je sporné a
řada webdesignerů preferuje jiný markup. Například definiční seznam.
Překonfigurujeme DefaultFormRenderer tak, aby formulář
v podobě seznamu vykreslil. Konfigurace se provádí editací pole $wrappers.
První index vždy představuje oblast a druhý její atribut. Jednotlivé
oblasti znázorňuje obrázek:

Standardně je skupina prvků controls obalena tabulkou
<table>, každý pair představuje řádek
tabulky <tr> a dvojice label a
control jsou buňky <th> a
<td>. Nyní obalující elementy změníme. Oblast
controls vložíme do kontejneru <dl>, oblast
pair necháme bez kontejneru, label vložíme do
<dt> a nakonec control obalíme značkami
<dd>:
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = NULL;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';
echo $form;
Výsledkem je tento HTML kód:
<dl>
<dt><label class="required" for="frm-name">Jméno:</label></dt>
<dd><input type="text" class="text" name="name" id="frm-name" value="" /></dd>
<dt><label class="required" for="frm-age">Věk:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" value="" /></dd>
<dt><label>Pohlaví:</label></dt>
...
</dl>
V poli wrappers lze ovlivnit celou řadu dalších atributů:
- přidávat CSS třídy jednotlivým typům formulářových prvků
- rozlišovat CSS třídou liché a sudé řádky
- vizuálně odlišit povinné a volitelné položky
- určovat, zda se chybové zprávy zobrazí přímo u prvků nebo nad formulářem
Manuální vykreslování
DefaultFormRenderer nám umožňuje bleskově renderovat formuláře se
standardním vzhledem. Jak ale vykreslit formulář, který lze jen těžko
postihnout polem $wrappers nebo vlastním rendererem? Asi
nejpragmatičtější řešení je takový formulář popsat přímo HTML
šablonou.
Jak už víte, jednotlivé prvky formuláře lze adresovat podobně jako
prvky pole (tj. třeba $form['name']). Co možná nevíte, tak že
každý prvek disponuje metodami getLabel() a
getControl(), které vracejí HTML kód popisky a samotného prvku.
Nette Framework dovoluje ke getterům přistupovat podobně, jako
by to byly proměnné, takže stačí psát jen label a
control. Dejme tyto informace dohromady a máme tu manuální
renderování:
<?php $form->render('begin') ?>
<?php $form->render('errors') ?>
<table>
<tr class="required">
<th><?php echo $form['name']->label ?></th>
<td><?php echo $form['name']->control ?></td>
</tr>
<tr class="required">
<th><?php echo $form['age']->label ?></th>
<td><?php echo $form['age']->control ?></td>
</tr>
...
</table>
<?php $form->render('end') ?>
V tuto chvíli jste naprostým pánem vygenerovaného kódu. Formulář si vykreslíte přesně na míru.
Když do vykreslování zapojíme ještě Latte, může výsledná šablona vypadat takto:
{form signForm}
<!-- Jednoduché vykreslení chyb -->
<ul class="errors" n:if="$form->hasErrors()">
<li n:foreach="$form->errors as $error">{$error}</li>
</ul>
<table>
<tr class="required">
<th>{label name /}</th>
<td>{input name}</td>
</tr>
...
</table>
{/form signForm}
S vykreslením prvků uvnitř formulářového kontejneru pomůže makro
formContainer.
{form signForm}
<table>
<th>Přeji si emailem odebírat tyto novinky:</th>
<td>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
</td>
...
</table>
{/form}
Jak nastavit HTML elementům další atributy? Metody
getControl() a getLabel() vrací element v podobě
Nette\Utils\Html objektu, se kterým se dá snadno pracovat. Takto například
v Latte:
{form signForm class => 'big'}
<table>
<tr class="required">
<th>{label name /}</th>
<td>{input name cols => 40, autofocus => TRUE}</td>
</tr>
Dokonce je možné jen „oživit“ prvky napsané čistě v HTML pomocí
makra n:input, které ho sváže s formulářovým prvek uvedením
jeho identifikátoru:
<table>
<tr class="required">
<th><label for="#frm-name"></th>
<td><input cols=40 n:input="name"></td>
</tr>
Další informace o vykreslování (metodě toggle) se dovíte na stránkce Vykreslování.
Seskupování prvků
Prvky lze seskupovat do vizuálních skupin (fieldsetů) vytvořením skupiny:
$form->addGroup('Personal data');
Po vytvoření nové skupiny se tato stává aktivní a každý nově přidaný prvek je zároveň přidán i do ní. Takže formulář lze stavět tímto způsobem:
$form = new Form;
$form->addGroup('Personal data');
$form->addText('name', 'Your name:');
$form->addText('age', 'Your age:');
$form->addText('email', 'Email:');
$form->addGroup('Shipping address');
$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:', 35);
$form->addText('city', 'City:', 35);
$form->addSelect('country', 'Country:', $countries);
Obrana před Cross-Site Request Forgery (CSRF)
Nette Framework ochrání vaše aplikace před útokem Cross-Site Request Forgery (CSRF). Útok spočívá v tom, že útočník naláká oběť na stránku, která nenápadně vykoná požadavek na server, na kterém je oběť přihlášena a server se domnívá, že požadavek vykonala oběť o své vůli.
Ochrana je velmi snadná:
$form->addProtection('Vypršel časový limit, odešlete formulář znovu');
Proti útoku se lze bránit generováním a ověřováním autorizačního tokenu. Ten má platnost po dobu existence session. Díky tomu nebrání použití ve více oknech najednou (v rámci jedné session). Platnost je však možné zkrátit na počet sekund, které se uvedou jako druhý parametr. První parametr je přitom text chybové hlášky, která se zobrazí uživateli, pokud token vypršel.
Obrana by měla být aktivována pro všechny formuláře, které mění citlivá data v aplikaci.
Vícejazyčné formuláře
Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat
stejný formulář vykreslit v různých jazykových mutacích. Formuláře
v Nette Framework disponují podporou pro snadný překlad. Stačí, když
formuláři nastavíte tzv. překladač, což je objekt implementující
rozhraní Nette\Localization\ITranslator.
Rozhraní má jedinou metodu translate().
class MyTranslator extends Nette\Object implements Nette\Localization\ITranslator
{
/**
* Translates the given string.
* @param string message
* @param int plural count
* @return string
*/
public function translate($message, $count = NULL)
{
return ...;
}
}
$form->setTranslator($translator);
V tu chvíli se nejen všechny popisky, chybové hlášky nebo položky select boxů transparentně přeloží do jiného jazyka.
Comments 
uestla | 23. 7. 2011, 0:40 | question
Jak vykreslit seznam příadných chyb uvnitř {form /} makra?
Zatím používám
{form formName}
{control $form errors}
{* ... *}
{/form}
Smee | 11. 8. 2011, 16:32 | comment
Proč se v prvním případě používá tento objekt
Nette\Forms\Form? Zatím co v presenteru je
Nette\Application\UI\Form?
V čem je rozdíl či jaké mají výhody?
Semik | 12. 8. 2011, 0:41 | comment
Skoro nahoře v poznámce je napsáno:
V presenterech se místo třídy Nette\Forms\Form používá od ní odvozená třída Nette\Application\UI\Form, kde se formulář oživuje trošku jiným způsobem. Více v části formuláře v presenterech.
dundee | 2. 9. 2011, 10:53 | comment
Nastavení classy elementu:
$form->addTextArea('body', 'Body')
->getControlPrototype()->class('xxx');
kahi | 7. 9. 2011, 22:35 | question
Není to možné / to není dokumentované / špatně hledám? Jak vložit za input něco jako poznámku/vysvětlivku? (Abych nemusel vykreslovat manuálně.)
rokerkony | 8. 9. 2011, 7:12 | comment
Je to možné, např.
$form->addText('date', 'Datum')
->setOption('description', 'Zadávejte ve formátu: DD-MM-RRRR HH:SS');
rokerkony | 8. 9. 2011, 7:32 | comment
přidám ještě odkaz na API… dá se toho nastavit víc: http://api.nette.org/…oup.php.html#76
josef.sabl | 17. 10. 2011, 23:24 | comment
Jediná zmínka o tom, jak zpracovat výsledek formuláře je:
$values = $form->getValues();
dump($values);
To by si asi zasloužilo rozšířit.
2bfree | 17. 11. 2011, 11:32 | comment
Ještě existuje zajímavá stránka. Škoda že zde na ní není odkaz. http://dev.nette.org/…vykreslovani
grey | 28. 11. 2011, 16:32 | bug
Nejnovější verze z gitu (67242d4a48ef63b90c79b1211c325fc494caf16c) hází při použití bez sandboxu (čiste jen require nette loaderu, jako je v prvním příkladu) toto: http://dl.dropbox.com/…22494021.png
Patrik Votoček | 28. 11. 2011, 18:58 | comment
@grey: v novém sandboxu jsou zpět konstanty pro definici cest. Místo „dočasných“ params.
grey | 28. 11. 2011, 19:15 | comment
@Patrik Votoček: Tady jde právě o použití bez sandboxu. :)
gavec | 15. 1. 2012, 13:39 | question
Má validace popsaná výše nějaké výhody se srovnáním např s jQuery validací? jQuery mi přijde uživatelský přívětivější. U této validace se mi nelibí vyskakovací okno s hláškou, které vyskočí až potom, co formulář uživatel potvrdí.
pcs | 29. 1. 2012, 18:43 | comment
Do dokumentace bych ještě doplnil addButton
Rampus | 16. 2. 2012, 9:10 | question
Možná to nějak jde a jen jsem to přehlédnul, ale docela by se hodilo makro na vykreslení celé grupu controlu. Něco jako:
{form MyForm}
{group user}
{group foo}
{group buttons} {/form}
Caine | 21. 2. 2012, 20:59 | comment
Skládání formulářů přes kompozici: http://forum.nette.org/…et-formulare#…

tatyalien | 15. 6. 2011, 8:48 | comment
U vlastní validace mě to nefungovalo, až když jsem si to přepsal dle frameworku custom-validator.php kde je: