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

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)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 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->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á',
);
$prompt = Html::el('option')->setText("Zvolte zemi")->class('prompt');
$form->addSelect('country', 'Země:', $countries)
    ->setPrompt($prompt);   // je možné předat text i prvek HTML

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('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), 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í, 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
        ->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
// 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.');
    }
    ...
}

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 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á.');
    }
}

JavaScript

Validační pravidla se na stranu JavaScriptu přenášejí v HTML atributech 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 nebo pole, uvedeme-li TRUE jako první parametr.

$values = $form->getValues();     // Nette\ArrayHash
$values = $form->getValues(TRUE); // array

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:')
    ->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.

Formulář přidáme do presenteru pomocí tzv. továrničky:

use Nette\Application\UI;
class HomepagePresenter extends UI\Presenter
{
    protected function createComponentSignInForm()
    {
        $form = new UI\Form;
        $form->addText('name', 'Jméno:');
        $form->addPassword('password', 'Heslo:');
        $form->addSubmit('login', 'Přihlásit se');
        $form->onSuccess[] = array($this, 'signInFormSubmitted');
        return $form;
    }

    // volá se po úspěšném odeslání formuláře
    public function signInFormSubmitted(UI\Form $form)
    {
        $values = $form->getValues();
        ...
        $this->flashMessage('Byl jsi úspěšně přihlášen.');
        $this->redirect('Homepage:');
    }
}

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ě.

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:

  1. vložit do hierarchie presenterů jejich společného předka a továrničku definovat tam
  2. 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[] = array($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:

$form->addCheckbox('agree', 'Agree with conditions')
    ->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>

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('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.