Validace formulářů

Validace formulářových prvků

Na formulářové prvky lze kromě validačních pravidel uvedených v přehledu prvků používat ještě tyto pravidla:

Form::FILLED je prvek vyplněn?
Form::REQUIRED alias pro Form::FILLED
Form::EQUAL je hodnota rovna uvedené? mixed $value nebo $values[]
Form::NOT_EQUAL prvek se nesmí rovnat zadané hodnotě mixed $value nebo $values[]
Form::IS_IN testuje, zda hodnota spadá do výčtu mixed $value nebo $values[]
Form::IS_NOT_IN hodnota nesmí spadat do výčtu mixed $value nebo $values[]
Form::VALID je prvek vyplněn správně?  
Form::BLANK prvek nesmí být vyplněn  

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 hodnotou

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->addEmail('email', 'E-mail:')
    // pokud je checkbox zaškrtnut
    ->addConditionOn($form['newsletters'], Form::EQUAL, true)
        // pak vyžaduj e-mail
        ->setRequired('Zadejte e-mailovou adresu');

Podmínky je možné negovat znakem ~ (vlnovka), tj. addCondition(~Form::NUMBER, ...). 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(BaseControl $item, $arg)
{
    return $item->getValue() % $arg === 0;
}

$form->addInteger('number', 'Číslo:')
    ->addRule('divisibilityValidator', 'Číslo musí být dělitelné %d.', 8);

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:

<script src="/path/to/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.

Úprava hodnot

Pomocí metody addFilter můžeme upravit hodnotu ještě před samotným zpracování formuláře. addFilter je možno kombinovat s metodami addCondition a addConditionOn.

$form->addText('zip', 'PSČ:')
    ->addCondition($form::FILLED)
    ->addFilter(function ($value) {
        return str_replace(' ', '', $value);
    });

Když poté přístoupíme ve zpracování formuláře k jeho hodnotám, PSČ již bude zbaveno mezer.

Chyby při zpracování

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

Doporučuje se připojit chybu přímo k prvku formuláře, protože se zobrazí vedle něj při použití výchozího rendereru.

$form['date']->addError("Omlouváme se, ale toto datum již je zabrané.");

Validace celého formuláře

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[] = [$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().

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([]). 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->addInteger('age')->setRequired('age');
$details->addInteger('age2')->setRequired('age2');

$form->addSubmit('send1'); // Validuje celý formuláře
$form->addSubmit('send2')->setValidationScope(false); // Nevaliduje vůbec
$form->addSubmit('send3')->setValidationScope([$form['name']]); // Validuje pouze pole name
$form->addSubmit('send4')->setValidationScope([$form['details']['age']]); // Validuje pouze pole age
$form->addSubmit('send5')->setValidationScope([$form['details']]); // Validuje kontejner details

setValidationScope neovlivní událost onValidate u formuláře, která bude zavolána vždy. Událost onValidate u kontejneru bude vyvolána pouze pokud je tento kontejner označen pro částečnou validaci.