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
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.
Použitím Nette\Forms se vyhneme celé řadě rutinních úkolů, jako je třeba psaní dvojí validace (na straně serveru a klienta), minimalizujeme pravděpodobnost vzniku chyb a bezpečnostních děr.
První formulář
Vytvoříme si v naší aplikaci jednoduchý registrační formulář. Přidáme ho do presenteru pomocí tzv. továrny:
use Nette\Application\UI;
class HomepagePresenter extends UI\Presenter
{
// ...
protected function createComponentRegistrationForm()
{
$form = new UI\Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('login', 'Registrovat');
$form->onSuccess[] = array($this, 'registrationFormSucceeded');
return $form;
}
// volá se po úspěšném odeslání formuláře
public function registrationFormSucceeded(UI\Form $form, $values)
{
// ...
$this->flashMessage('Byl jste úspěšně registrován.');
$this->redirect('Homepage:');
}
}
v šabloně ho vykreslíme makrem control:
{control registrationForm}
a v prohlížeči bude vypadat takto:

Vytvořili jsme formulář, který po odeslání a úspěšné validaci zavolá metodu
registrationFormSucceeded()
. Jako první parametr této metody je předán samotný formulář. Do druhého parametru
se předávají odeslané hodnoty formuláře v objektu Nette\Utils\ArrayHash. Pokud chceme obdržet místo objektu pole,
dáme parametru typehint array $values
. Pro získání odeslaných hodnot můžeme také použít funkci
$values = $form->getValues($asArray = FALSE)
.
V rámci životního cyklu presenteru dochází ke
zpracování formuláře na stejné úrovni jako zpracování signálů (metody handle*
), tedy po
action*
metodě a před render*
metodou.
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.
Data v proměnné $values
neobsahují hodnoty formulářových tlačítek, takže 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. Formulář se automaticky validuje na straně klienta i na straně serveru.
Pokud nevycházíte z nette/sandbox, musíte pro zprovoznění JavaScript validace zalinkovat soubor netteForms.js, který najdete ve složce src/assets.
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)
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 |
délka v daném rozsahu nebo právě tato délka |
Form::EMAIL |
je hodnota platná e-mailová adresa? |
Form::URL |
je hodnota absolutní URL? |
Form::PATTERN |
testuje oproti regulárnímu výrazu celou hodnotu, tj. jako by byl obalen znaky ^ a $ |
Form::INTEGER |
je hodnota celočíselná? |
Form::FLOAT |
je hodnota číslo? |
Form::RANGE |
je hodnota v daném rozsahu? |
Form::BLANK |
prvek nesmí být vyplněn |
Form::NOT_EQUAL |
prvek se nesmí rovnat zadané hodnotě |
$form->addText('zip', 'PSČ:')
->addRule(Form::PATTERN, 'PSČ musí mít 5 číslic', '([0-9]\s*){5}');
Validační pravidla Form::INTEGER
, NUMERIC
a FLOAT
rovnou převádí
hodnotu na integer resp. float. A dále pravidlo Form::URL, které akceptuje i řetězec ve tvaru např. nette.org
,
jej automaticky doplní na plnohodnotné https://nette.org.
addPassword($name, $label = 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 */);
$form->addUpload('avatar', 'Avatar:', TRUE);
addMultiUpload($name, $label = NULL)
Přidá políčko, které uživateli umožní nahrát více souborů najednou. Tato možnost je dostupná od verze 2.2.3,
v dřívějších verzích se přidává pomocí `addUpload($name, $label, $multiple = TRUE); Validační pravidla jsou stejné,
jako u addUpload()
a přidává následující pravidla:
Form::MIN_LENGTH |
minimální počet souborů |
Form::MAX_LENGTH |
maximální počet souborů |
Form::LENGTH |
počet souborů v daném rozsahu nebo právě tato délka |
$form->addMultiUpload('files', 'Soubory'); // Od verze 2.2.3
$form->addUpload('files', 'Soubory', $multiple = TRUE); // Dříve
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);
addCheckboxList($name, $label = NULL, array $items = NULL)
Přidá seznam zaškrtávacích políček pro výběr více prvků. Pole nabízených hodnot opět předáme jako třetí parametr. Stejně jako v případě selectboxů nebo radiolistů kontroluje, zda odeslané hodnoty jsou z těch, které nabízíme.
$form = new Form;
$form->addCheckboxList('colors', 'Barvy:', array(
'r' => 'červená',
'g' => 'zelená',
'b' => 'modrá',
));
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');
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('raise', 'Zvýšit plat')
->setAttribute('onclick', 'raiseSalary()');
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), nebo-li
kontejner, do kterého 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:');
Low-level formuláře
Od verze 2.1 lze používat i prvky, které zapíšeme pouze v šabloně a nepřidáme je do formuláře některou z metod
$form->addXyz()
. Když například vypisujeme záznamy z databáze a dopředu nevíme, kolik jich bude a jaké
budou mít ID, a chceme u každého řádku zobrazit checkbox nebo radio button, stačí jej nakódovat v šabloně:
{foreach $items as $item}
<p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p>
{/foreach}
A po odeslání hodnotu zjistíme:
$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');
$values = $form->getHttpData($form::DATA_TEXT | $form::DATA_KEYS, 'sel[]');
kde první parametr je typ elementu (DATA_FILE
pro type=file
, DATA_LINE
pro
jednořádkové vstupy jako text
, password
, email
apod. a DATA_TEXT
pro
všechny ostatní) a druhý parametr sel[]
odpovídá HTML atributu name. Typ elementu můžeme kombinovat
s hodnotou DATA_KEYS
, která zachová klíče prvků. To se hodí zejména pro select
,
radioList
a checkboxList
.
Podstatné je, že getHttpData()
vrací sanitizovanou hodnotu, v tomto případě to bude vždy pole validních
UTF-8 řetězců, ať už se pokusíte serveru podstrčit cokoliv. Jde o obdobu přímé práce s $_POST
nebo
$_GET
avšak s tím podstatným rozdílem, že vždy vrací čistá data, tak, jak jste zvyklí u standardních
prvků Nette formulářů.
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í, kterou můžete případně přepsat. 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
->setRequired('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
// poznámka: toto je skutečná funkce, nikoliv metoda v presenteru
function divisibilityValidator($item, $arg)
{
return $item->value % $arg === 0;
}
$form->addText('number', 'Číslo:')
->addRule('divisibilityValidator', 'Číslo musí být dělitelné %d.', 8);
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) {
if ($e->getCode() === Nette\Security\IAuthenticator::INVALID_CREDENTIAL) {
$form->addError('Neplatné heslo.');
}
...
}
Vlastní validační funkce
Pokud z nějakého důvodu potřebujete přidat validační funkcionalitu, typicky ověření správné kombinace hodnot ve
více prvcích formuláře, můžete si napsat vlastní validační funkce. Ty pak na formulář navážete pomocí události
onValidate
:
protected function createComponentSignInForm()
{
$form = new Form();
...
$form->onValidate[] = array($this, 'validateSignInForm');
return $form;
}
public function validateSignInForm($form)
{
$values = $form->getValues();
if (...) { // validační podmínka
$form->addError('Tato kombinace není možná.');
}
}
Na událost onValidate
můžete registrovat libovolný počet funkcí. Funkce je chápána jako úspěšná, pokud
nepřidá do formuláře chybu pomocí $form->addError()
.
JavaScript
Validační pravidla se na stranu JavaScriptu přenášejí v HTML atributech data-nette-rules
, které obsahují
JSON 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 src/assets
. 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>
Pokud náš validační callback v PHP je statická metoda ve třídě, tak název pro JavaScriptový validátor vytvoříme
smazáním zpětných lomítek \
a nahrazením dvojité dvojtečky za jedno podtržítko _
, např.
App\MyValidator::divisibilityValidator
zapíšeme jako AppMyValidator_divisibilityValidator
.
Vypnutí validace
Někdy se může hodit validaci vypnout. Pokud stisknutí odesílacího tlačítka nemá provádět validaci (vhodné pro
tlačítka Cancel nebo Preview), vypneme ji metodou $submit->setValidationScope(array())
. Pokud má
provádět validaci jen částečnou, můžeme určit které pole nebo formulářové kontejnery se mají validovat.
$form->addText('name')->setRequired();
$details = $form->addContainer('details');
$details->addText('age')->setRequired('age');
$details->addText('age2')->setRequired('age2');
$form->addSubmit('send1'); // Validuje celý formuláře
$form->addSubmit('send2')->setValidationScope(FALSE); // Nevaliduje vůbec
$form->addSubmit('send3')->setValidationScope(array($form['name'])); // Validuje pouze pole name
$form->addSubmit('send4')->setValidationScope(array($form['details']['age'])); // Validuje pouze pole age
$form->addSubmit('send5')->setValidationScope(array($form['details'])); // Validuje kontejner details
Zpracování hodnot
K hodnotám formuláře se dostaneme pomocí metody getValues(), která nám vrací objekt typu ArrayHash nebo pole, uvedeme-li TRUE
jako první
parametr.
$values = $form->getValues(); // Nette\Utils\ArrayHash
$values = $form->getValues(TRUE); // array
Deaktivace prvků
Pokud chceme některý prvek deaktivovat, můžeme využít metodu $control->setDisabled(TRUE)
$form->addText('email', 'E-mail:')->setDisabled(TRUE);
Do tohoto prvku nepůjde zapisovat a jeho hodnota nebude obsažena v datech vracených funkcí
$form->getValues()
.
Pokud potřebujeme prvek pouze vyjmout z těchto dat, použijeme funkci $control->setOmitted(TRUE)
. To se
hodí pro různá hesla pro kontrolu, antispamové prvky atd.
$form->addText('antispam', 'Antispam:')->setOmitted(TRUE);
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 JavaScriptu:
$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:')
->setType('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', $label)
.
Formuláře v presenterech
V presenterech se místo třídy Nette\Forms\Form
používá od ní odvozená třída Nette\Application\UI\Form.
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árnu definovat tam
- nebo definovat formulář v samostatné tovární třídě a v jednotlivých továrnách vytvářet jeho instance.
Vhodné umístění pro takovou třídu je např. app/forms/SignInFormFactory.php
. Naše tovární třída bude
vypadat takto:
use Nette\Application\UI\Form;
class SignInFormFactory
{
/**
* @return Form
*/
public function create()
{
$form = new Form;
$form->addText('name', 'Jméno:');
// ...
$form->addSubmit('login', 'Přihlásit se');
return $form;
}
}
V továrničce každého presenteru, který náš formulář používá, jej následně vytvoříme voláním metody
create()
:
protected function createComponentSignInForm()
{
$form = (new SignInFormFactory())->create();
$form['login']->caption = 'Pokračovat'; // můžeme také formulář pozměnit
$form->onSuccess[] = array($this, 'signInFormSubmitted'); // a přidat událost po odeslání
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
, případně
použijeme anonymní funkci:
use Nette\Application\UI\Form;
class SignInFormFactory
{
/**
* @return Form
*/
public function create()
{
$form = new Form;
$form->addText('name', 'Jméno:');
...
$form->addSubmit('login', 'Přihlásit se');
$form->onSuccess[] = function (Form $form, \stdClass $values) {
// zde provedeme zpracování formuláře
};
return $form;
}
}
Odesílá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[] = array($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ě.
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:
$form->addCheckbox('agree', 'Agree with conditions')
->setDefaultValue(TRUE);
Další užitečnou možností je použití „emptyValue“. Pokud je hodnota prvku po odeslání formuláře shodná s nastavenou „emptyValue“, tváří se prvek jako nevyplňený.
$form->addText('phone', 'Phone:')
->setEmptyValue('+42');
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
Podpora pro Bootstrap
V příkladech najdete ukázky, jak nakonfigurovat vykreslování formulářů pro Twitter Bootstrap 2 a Bootstrap 3
Manuální vykreslování
Formuláře lze vykreslovat i ručně a tím získat větší kontrolu nad kódem. Celý formulář umístíme mezi párové
makra {form myForm}
a {/form}
. Jednotlivé prvky můžeme vypisovat pomocí maker
{input myInput}
, které vypíše formulářový prvek, a {label myLabel /}
, které vypíše jeho
popisek.
{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}
Velmi snadno také můžete propojit formulář s existující šablonou. Stačí jen doplnit atributy n:name:
function createComponentSignInForm()
{
$form = new Form;
$form->addText('user')->setRequired();
$form->addPassword('password')->setRequired();
$form->addSubmit('send');
return $form;
}
<form n:name=signInForm class=form>
<p><label n:name=user>Username: <input n:name=user size=20></label>
<p><label n:name=password>Password: <input n:name=password></label>
<p><input n:name=send class="btn btn-default">
</form>
Atribut n:name
lze používat i s elementy <select>
, <button>
nebo
<textarea>
a vnitřní obsah se automaticky doplní.
Dále můžete vykreslovat prvky jako je RadioList, Checkbox nebo CheckboxList pěkně po jednotlivých HTML elementech. Říká se tomu partial rendering:
{foreach $form[gender]->items as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
Nebo lze použít klasická makra {input gender:$key}
a {label gender:$key}
, trik je tom názvu
s dvojtečkou. Pro jednoduchý checkbox použijte {input myCheckbox:}
.
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>
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:');
$form->addText('city', 'City:');
$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('Vaše relace vypršela. Vraťte se na domovskou stránku a zkuste to znovu.');
Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci.
Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session
(parametrem je text chybové hlášky, která se zobrazí uživateli, pokud token vypršel). Proto je nutné před zobrazením
formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení
uživatele. Jinak session nastartujte metodou Nette\Http\Session::start()
, uvnitř presenteru můžete použít
$this->getSession()->start()
. Také lze zapnout session pro celou aplikaci pomocí konfigurace autoStart.
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.
Samostatné použití Nette\Forms
Pokud z nějakého důvodu nechcete používat celý framework, můžete využít Nette/Forms samostatně. Nainstalujete je pomocí Composeru.
composer require nette/forms
Vytvoření formuláře potom vypadá asi takto:
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('send', 'Registrovat');
echo $form; // vykreslí formulář
Takto vytvořený formulář se metodou POST odešle na stejnou stránku. To se dá snadno změnit:
$form = new Form;
$form->setAction('/submit.php');
$form->setMethod('GET');
...
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);
}
K jednotlivým prvkům formuláře $form
lze 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.
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.
Vykreslení formulář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
.
<?php $form->render('begin') ?>
<?php $form->render('errors') ?>
<table>
<tr class="required">
<th><?php echo $form['name']->label // Zavolá getLabel() ?></th>
<td><?php echo $form['name']->control // Zavolá getControl() ?></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') ?>
Související články na blogu
Novinky v Nette Forms 3.1
Ochrana před CSRF pomocí cookie Zranitelnost CSRF spočívá v tom, že formulář je podvržen útočníkem a odeslán z jeho stránky, tedy i z jiné domény,…
Ekonomie, kontejnery a další žhavé novinky v Nette Forms
Přehled nejdůležitějších novinek v balíčku nette/forms 3.0.x. Ekonomické checkbox listy CheckboxList odesílaný metodou GET se nepřenáší zrovna…
Formuláře v Nette 2.1
Formuláře byly vždy klíčovou součástí frameworku. Od první verze umožňují vývojářům snadno definovat vstupní prvky, vykreslit je a zpracovávat data…