Nette Documentation Preview

syntax
Validace formulářů
******************


Povinné prvky
=============

Povinné prvky označíme metodou `setRequired()`, jejíž argument je text [#chybové hlášky], která se zobrazí, pokud uživatel prvek nevyplní. Pokud argument neuvedeme, použije se výchozí chybová hláška.

```php
$form->addText('name', 'Jméno:')
	->setRequired('Zadejte prosím jméno');
```


Pravidla
========

Validační pravidla přidáváme prvkům metodou `addRule()`. První parametr je pravidlo, druhý je text [#chybové hlášky] a třetí je argument validačního pravidla.

```php
$form->addPassword('password', 'Heslo:')
	->addRule($form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků', 8);
```

Nette přichází s celou řadu předdefinovaných pravidel, jejichž názvy jsou konstanty třídy `Nette\Forms\Form`.

U všech prvků můžeme použít tyto pravidla:

| konstanta | popis | typ argumentu
|-------
| `REQUIRED` | povinný prvek, alias pro `setRequired()` | -
| `FILLED` | povinný prvek, alias pro `setRequired()` | -
| `BLANK` | prvek nesmí být vyplněn | -
| `EQUAL` | hodnota se rovná parametru | `mixed`
| `NOT_EQUAL` | hodnota se nerovná parametru | `mixed`
| `IS_IN` | hodnota se rovná některé položce v poli | `array`
| `IS_NOT_IN` | hodnota se nerovná žádné položce v poli | `array`
| `VALID` | je prvek vyplněn správně? (pro [#podmínky]) | -

U prvků `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()` lze použít i následující pravidla:

| `MIN_LENGTH` | minimální délka textu | `int`
| `MAX_LENGTH` | maximální délka textu | `int`
| `LENGTH` | délka v rozsahu nebo přesná délka | dvojice `[int, int]` nebo `int`
| `EMAIL` | platná e-mailová adresa | -
| `URL` | absolutní URL | -
| `PATTERN` | vyhovuje regulárnímu výrazu | `string`
| `PATTERN_ICASE` | jako `PATTERN`, ale nezávislé na velikosti písmen | `string`
| `INTEGER` | celočíselná hodnota | -
| `NUMERIC` | alias pro `INTEGER` | -
| `FLOAT` | číslo | -
| `MIN` | minimální hodnota číselného prvku | `int\|float`
| `MAX` | maximální hodnota číselného prvku | `int\|float`
| `RANGE` | hodnota v rozsahu | dvojice `[int\|float, int\|float]`

Validační pravidla `INTEGER`, `NUMERIC` a `FLOAT` rovnou převádí hodnotu na integer resp. float. A dále pravidlo `URL` akceptuje i adresu bez schématu (např. `nette.org`) a schéma doplní (`https://nette.org`).
Výraz v `PATTERN` a `PATTERN_ICASE` musí platit pro celou hodnotu, tj. jako by byl obalen znaky `^` a `$`.

U prvků `addUpload()`, `addMultiUpload()` lze použít i následující pravidla:

| `MAX_FILE_SIZE` | maximální velikost souboru | `int`
| `MIME_TYPE` | MIME type, povoleny zástupné znaky (`'video/*'`) | `string\|string[]`
| `IMAGE` | obrázek JPEG, PNG, GIF, WebP | -
| `PATTERN` | jméno souboru vyhovuje regulárnímu výrazu | `string`
| `PATTERN_ICASE` | jako `PATTERN`, ale nezávislé na velikosti písmen | `string`

`MIME_TYPE` a `IMAGE` vyžadují PHP rozšíření `fileinfo`. Že je soubor či obrázek požadovaného typu detekují na základě jeho signatury a **neověřují integritu celého souboru.** Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|/http/request#toImage].

U prvků `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()` lze použít i následující pravidla pro omezení počtu vybraných položek resp. uploadovaných souborů:

| `MIN_LENGTH` | minimální počet  | `int`
| `MAX_LENGTH` | maximální počet  | `int`
| `LENGTH` | počet v rozsahu nebo přesný počet | dvojice `[int, int]` nebo `int`


Chybové hlášky
--------------

Všechny předdefinované pravidla s výjimkou `PATTERN` a `PATTERN_ICASE` mají výchozí chybovou hlášku, takže ji lze vynechat. Nicméně uvedením a formulací všech hlášek na míru uděláte formulář uživatelsky přívětivější.

Změnit výchozí hlášky můžete v [konfiguraci|/forms/configuration], úpravou textů v poli `Nette\Forms\Validator::$messages` nebo použitím [translatoru|/best-practices/translations#Překlad formulářů].

V textu chybových hlášek lze používat tyto zástupné řetězce:

| `%d`     | nahradí postupně za argumenty pravidla
| `%n$d`   | nahradí za n-tý argument pravidla
| `%label` | nahradí za popisku prvku (bez dvojtečky)
| `%name`  | nahradí za jméno prvku (např. `name`)
| `%value` | nahradí za uživatelem vloženou hodnotu

```php
$form->addText('name', 'Jméno:')
	->setRequired('Vyplňte prosím %label');

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejméně %d a nejvíce %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejvíce %2$d a nejméně %1$d', [5, 10]);
```


Podmínky
========

Kromě pravidel lze přidávat také 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á):

```php
$form->addPassword('password', 'Heslo:')
	// pokud není heslo delší než 8 znaků
	->addCondition($form::MAX_LENGTH, 8)
		// pak musí obsahovat číslici
		->addRule($form::PATTERN, 'Musí obsahovat číslici', '.*[0-9].*');
```

Podmínku je možné vázat i na jiný prvek než aktuální pomocí `addConditionOn()`. Jako první parametr uvedeme referenci na prvek. V této ukázce bude e-mail povinný jen tehdy, zaškrtne-li se checkbox (jeho hodnota bude true):

```php
$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');
```

Z podmínek lze vytvářet komplexní struktury za pomoci `elseCondition()` a `endCondition()`:

```php
$form->addText(/* ... */)
	->addCondition(/* ... */) // je-li splněna první podmínka
		->addConditionOn(/* ... */) // a druhá podmínka na jiném prvku
			->addRule(/* ... */) // vyžaduj toto pravidlo
		->elseCondition() // pokud druhá podmínka splněná není
			->addRule(/* ... */) // vyžaduj tato pravidla
			->addRule(/* ... */)
		->endCondition() // vracíme se k první podmínce
		->addRule(/* ... */);
```

V Nette lze velmi snadno reagovat na splnění či nesplnění podmíny i na straně JavaScriptu pomocí metody `toggle()`, viz [#dynamický JavaScript].


Reference mezi prvky
====================

Jako argument pravidla či podmínky lze uvádět referenci na jiný prvek. Takto lze např. dynamicky validovat, že prvek `text` má tolik znaků, jako je hodnota prvku `length`:

```php
$form->addInteger('length');
$form->addText('text')
	->addRule($form::LENGTH, null, $form['length']);
```


Vlastní pravidla a podmínky
===========================

Občas se dostaneme do situace, kdy nám vestavěná validační pravidla v Nette nestačí a potřebujeme data od uživatele validovat po svém. V Nette je to velmi jednoduché!

Metodám `addRule()` či `addCondition()` lze první parametr předat libovolný callback. Ten přijímá jako první parametr samotný prvek a vrací boolean hodnotu určující, zda validace proběhla v pořádku. Při přidávání pravidla pomocí `addRule()` je možné zadat i další argumenty, ty jsou pak předány jako druhý parametr.

Vlastní sadu validátorů tak můžeme vytvořit jako třídu se statickými metodami:

```php
class MyValidators
{
	// testuje, zda je hodnota dělitelná argumentem
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// další validátory
	}
}
```

Použití je pak velmi jednoduché:

```php
$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'Hodnota musí být násobkem čísla %d',
		8
	);
```

Vlastní validační pravidla lze přidávat i do JavaScriptu. Podmínkou je, aby pravidlo byla statická metoda. Její název pro JavaScriptový validátor vznikne spojením názvu třídy bez zpětných lomítek `\`, podtržítka `_` a názvu metody. Např. `App\MyValidators::validateDivisibility` zapíšeme jako `AppMyValidators_validateDivisibility` a přidáme do objektu `Nette.validators`:

```js
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};
```


Událost onValidate
==================

Po odeslání formuláře se provádí validace, kdy se zkontrolují jednotlivá pravidla přidaná pomocí `addRule()` a následně se vyvolá [událost|/glossary#Události] `onValidate`. Její handler lze využít k doplňkové validaci, typicky ověření správné kombinace hodnot ve více prvcích formuláře.

Pokud se odhalí chyba, předáme ji do formuláře metodou `addError()`. Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři.

```php
protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;
}

public function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('Tato kombinace není možná.');
	}
}
```


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 opět předáme do formuláře metodou `addError()`. Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři:

```php
try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Homepage:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::INVALID_CREDENTIAL) {
		$form->addError('Neplatné heslo.');
	}
	// ...
}
```

Pokud je to možné, doporučujeme připojit chybu přímo k prvku formuláře, protože se zobrazí vedle něj při použití výchozího rendereru.

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

Můžete `addError()` volat opakovaně a tak předat formuláři nebo prvku více chybových zpráv. Získáte je pomocí `getErrors()`.

Pozor, `$form->getErrors()` vrací sumář všech chybových zpráv, i těch, co byly předány přímo jednotlivým prvkům, nejen přímo formuláři. Chybové zprávy předané pouze formuláři získáte přes `$form->getOwnErrors()`.


Úprava vstupu
=============

Pomocí metody `addFilter()` můžeme pozměnit uživatelem vloženou hodnotu. V této ukázce budeme tolerovat a odstraňovat mezery v PSČ:

```php
$form->addText('zip', 'PSČ:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // odstraníme mezery z PSČ
	})
	->addRule($form::PATTERN, 'PSČ není ve tvaru pěti číslic', '\d{5}');
```

Filtr se začlení mezi validační pravidla a podmínky a tedy záleží na pořadí metod, tj. filtr a pravidlo se volají v takovém pořadí, jako je pořadí metod `addFilter()` a `addRule()`.


JavaScriptová validace
======================

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. Přenáší se v HTML atributech `data-nette-rules` jako JSON.
Samotnou validaci pak provádí skript, který odchytí událost formuláře `submit`, projde jednotlivé prvky a vykoná příslušnou validaci.

Tím skriptem je `netteForms.js` a je dostupný z více možných zdrojů:

Skript můžete vložit přímo do HTML stránky ze CDN:

```html
<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script>
```

Nebo zkopírovat lokálně do veřejné složky projektu (např. z `vendor/nette/forms/src/assets/netteForms.min.js`):

```html
<script src="/path/to/netteForms.min.js"></script>
```

Nebo nainstalovat přes [npm|https://www.npmjs.com/package/nette-forms]:

```bash
npm install nette-forms
```

A následně načíst a spustit:

```js
import netteForms from 'nette-forms';
netteForms.initOnLoad();
```

Alternativně jej můžete načíst přímo ze složky `vendor`:

```js
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
```


Dynamický JavaScript
====================

Chcete zobrazit políčka pro zadání adresy pouze pokud uživatel zvolí zboží zaslat poštou? Žádný problém. Klíčem je dvojice metod `addCondition()` & `toggle()`:

```php
$form->addCheckbox('send_it')
	->addCondition($form::EQUAL, true)
		->toggle('#address-container');
```

Tento kód říká, že když je podmínka splněná, tedy když je zaškrtnutý checkbox, bude viditelný HTML element `#address-container`. A obráceně. Prvky formuláře s adresou příjemce tak umístíme do kontejneru s tímto ID a při kliknutí na checkbox se skryjí nebo zobrazí. To zajišťuje skript `netteForms.js`.

Jako argument metody `toggle()` je možné předat libovolný selektor. Z historických důvodů se alfanumerický řetězec bez dalších speciálních znaků chápe jako ID prvku, tedy stejně, jako by mu předcházel znak `#`. Druhý nepovinný parametr umožňuje obrátit chování, tj. pokud bychom použili `toggle('#address-container', false)`, element by se naopak zobrazil pouze tehdy, pokud by checkbox zaškrtnutý nebyl.

Výchozí implementace v JavaScriptu mění elementům property `hidden`. Chování však můžeme snadno změnit, například přidat animaci. Stačí v JavaScriptu přepsat metodu `Nette.toggle` vlastním řešením:

```js
Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// skryjeme nebo zobrazime 'el' dle hodnoty 'visible'
	});
};
```


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.

```php
$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([]); // Nevaliduje vůbec
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Validuje pouze prvek name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Validuje pouze prvek 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.


{{composer: nette/forms}}

Validace formulářů

Povinné prvky

Povinné prvky označíme metodou setRequired(), jejíž argument je text chybové hlášky, která se zobrazí, pokud uživatel prvek nevyplní. Pokud argument neuvedeme, použije se výchozí chybová hláška.

$form->addText('name', 'Jméno:')
	->setRequired('Zadejte prosím jméno');

Pravidla

Validační pravidla přidáváme prvkům metodou addRule(). První parametr je pravidlo, druhý je text chybové hlášky a třetí je argument validačního pravidla.

$form->addPassword('password', 'Heslo:')
	->addRule($form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků', 8);

Nette přichází s celou řadu předdefinovaných pravidel, jejichž názvy jsou konstanty třídy Nette\Forms\Form.

U všech prvků můžeme použít tyto pravidla:

konstanta popis typ argumentu
REQUIRED povinný prvek, alias pro setRequired()
FILLED povinný prvek, alias pro setRequired()
BLANK prvek nesmí být vyplněn
EQUAL hodnota se rovná parametru mixed
NOT_EQUAL hodnota se nerovná parametru mixed
IS_IN hodnota se rovná některé položce v poli array
IS_NOT_IN hodnota se nerovná žádné položce v poli array
VALID je prvek vyplněn správně? (pro podmínky)

U prvků addText(), addPassword(), addTextArea(), addEmail(), addInteger() lze použít i následující pravidla:

MIN_LENGTH minimální délka textu int
MAX_LENGTH maximální délka textu int
LENGTH délka v rozsahu nebo přesná délka dvojice [int, int] nebo int
EMAIL platná e-mailová adresa
URL absolutní URL
PATTERN vyhovuje regulárnímu výrazu string
PATTERN_ICASE jako PATTERN, ale nezávislé na velikosti písmen string
INTEGER celočíselná hodnota
NUMERIC alias pro INTEGER
FLOAT číslo
MIN minimální hodnota číselného prvku int|float
MAX maximální hodnota číselného prvku int|float
RANGE hodnota v rozsahu dvojice [int|float, int|float]

Validační pravidla INTEGER, NUMERIC a FLOAT rovnou převádí hodnotu na integer resp. float. A dále pravidlo URL akceptuje i adresu bez schématu (např. nette.org) a schéma doplní (https://nette.org). Výraz v PATTERN a PATTERN_ICASE musí platit pro celou hodnotu, tj. jako by byl obalen znaky ^ a $.

U prvků addUpload(), addMultiUpload() lze použít i následující pravidla:

MAX_FILE_SIZE maximální velikost souboru int
MIME_TYPE MIME type, povoleny zástupné znaky ('video/*') string|string[]
IMAGE obrázek JPEG, PNG, GIF, WebP
PATTERN jméno souboru vyhovuje regulárnímu výrazu string
PATTERN_ICASE jako PATTERN, ale nezávislé na velikosti písmen string

MIME_TYPE a IMAGE vyžadují PHP rozšíření fileinfo. Že je soubor či obrázek požadovaného typu detekují na základě jeho signatury a neověřují integritu celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho načtení.

U prvků addMultiUpload(), addCheckboxList(), addMultiSelect() lze použít i následující pravidla pro omezení počtu vybraných položek resp. uploadovaných souborů:

MIN_LENGTH minimální počet int
MAX_LENGTH maximální počet int
LENGTH počet v rozsahu nebo přesný počet dvojice [int, int] nebo int

Chybové hlášky

Všechny předdefinované pravidla s výjimkou PATTERN a PATTERN_ICASE mají výchozí chybovou hlášku, takže ji lze vynechat. Nicméně uvedením a formulací všech hlášek na míru uděláte formulář uživatelsky přívětivější.

Změnit výchozí hlášky můžete v konfiguraci, úpravou textů v poli Nette\Forms\Validator::$messages nebo použitím translatoru.

V textu chybových hlášek lze používat tyto zástupné řetězce:

%d nahradí postupně za argumenty pravidla
%n$d nahradí za n-tý argument pravidla
%label nahradí za popisku prvku (bez dvojtečky)
%name nahradí za jméno prvku (např. name)
%value nahradí za uživatelem vloženou hodnotu
$form->addText('name', 'Jméno:')
	->setRequired('Vyplňte prosím %label');

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejméně %d a nejvíce %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejvíce %2$d a nejméně %1$d', [5, 10]);

Podmínky

Kromě pravidel lze přidávat také 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ž 8 znaků
	->addCondition($form::MAX_LENGTH, 8)
		// pak musí obsahovat číslici
		->addRule($form::PATTERN, 'Musí obsahovat číslici', '.*[0-9].*');

Podmínku je možné vázat i na jiný prvek než aktuální pomocí addConditionOn(). Jako první parametr uvedeme referenci na prvek. V této ukázce bude e-mail povinný jen tehdy, zaškrtne-li se checkbox (jeho 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');

Z podmínek lze vytvářet komplexní struktury za pomoci elseCondition() a endCondition():

$form->addText(/* ... */)
	->addCondition(/* ... */) // je-li splněna první podmínka
		->addConditionOn(/* ... */) // a druhá podmínka na jiném prvku
			->addRule(/* ... */) // vyžaduj toto pravidlo
		->elseCondition() // pokud druhá podmínka splněná není
			->addRule(/* ... */) // vyžaduj tato pravidla
			->addRule(/* ... */)
		->endCondition() // vracíme se k první podmínce
		->addRule(/* ... */);

V Nette lze velmi snadno reagovat na splnění či nesplnění podmíny i na straně JavaScriptu pomocí metody toggle(), viz dynamický JavaScript.

Reference mezi prvky

Jako argument pravidla či podmínky lze uvádět referenci na jiný prvek. Takto lze např. dynamicky validovat, že prvek text má tolik znaků, jako je hodnota prvku length:

$form->addInteger('length');
$form->addText('text')
	->addRule($form::LENGTH, null, $form['length']);

Vlastní pravidla a podmínky

Občas se dostaneme do situace, kdy nám vestavěná validační pravidla v Nette nestačí a potřebujeme data od uživatele validovat po svém. V Nette je to velmi jednoduché!

Metodám addRule() či addCondition() lze první parametr předat libovolný callback. Ten přijímá jako první parametr samotný prvek a vrací boolean hodnotu určující, zda validace proběhla v pořádku. Při přidávání pravidla pomocí addRule() je možné zadat i další argumenty, ty jsou pak předány jako druhý parametr.

Vlastní sadu validátorů tak můžeme vytvořit jako třídu se statickými metodami:

class MyValidators
{
	// testuje, zda je hodnota dělitelná argumentem
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// další validátory
	}
}

Použití je pak velmi jednoduché:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'Hodnota musí být násobkem čísla %d',
		8
	);

Vlastní validační pravidla lze přidávat i do JavaScriptu. Podmínkou je, aby pravidlo byla statická metoda. Její název pro JavaScriptový validátor vznikne spojením názvu třídy bez zpětných lomítek \, podtržítka _ a názvu metody. Např. App\MyValidators::validateDivisibility zapíšeme jako AppMyValidators_validateDivisibility a přidáme do objektu Nette.validators:

Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};

Událost onValidate

Po odeslání formuláře se provádí validace, kdy se zkontrolují jednotlivá pravidla přidaná pomocí addRule() a následně se vyvolá událost onValidate. Její handler lze využít k doplňkové validaci, typicky ověření správné kombinace hodnot ve více prvcích formuláře.

Pokud se odhalí chyba, předáme ji do formuláře metodou addError(). Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři.

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;
}

public function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('Tato kombinace není možná.');
	}
}

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 opět předáme do formuláře metodou addError(). Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři:

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Homepage:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::INVALID_CREDENTIAL) {
		$form->addError('Neplatné heslo.');
	}
	// ...
}

Pokud je to možné, doporučujeme 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é.');

Můžete addError() volat opakovaně a tak předat formuláři nebo prvku více chybových zpráv. Získáte je pomocí getErrors().

Pozor, $form->getErrors() vrací sumář všech chybových zpráv, i těch, co byly předány přímo jednotlivým prvkům, nejen přímo formuláři. Chybové zprávy předané pouze formuláři získáte přes $form->getOwnErrors().

Úprava vstupu

Pomocí metody addFilter() můžeme pozměnit uživatelem vloženou hodnotu. V této ukázce budeme tolerovat a odstraňovat mezery v PSČ:

$form->addText('zip', 'PSČ:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // odstraníme mezery z PSČ
	})
	->addRule($form::PATTERN, 'PSČ není ve tvaru pěti číslic', '\d{5}');

Filtr se začlení mezi validační pravidla a podmínky a tedy záleží na pořadí metod, tj. filtr a pravidlo se volají v takovém pořadí, jako je pořadí metod addFilter() a addRule().

JavaScriptová validace

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. Přenáší se v HTML atributech data-nette-rules jako JSON. Samotnou validaci pak provádí skript, který odchytí událost formuláře submit, projde jednotlivé prvky a vykoná příslušnou validaci.

Tím skriptem je netteForms.js a je dostupný z více možných zdrojů:

Skript můžete vložit přímo do HTML stránky ze CDN:

<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script>

Nebo zkopírovat lokálně do veřejné složky projektu (např. z vendor/nette/forms/src/assets/netteForms.min.js):

<script src="/path/to/netteForms.min.js"></script>

Nebo nainstalovat přes npm:

npm install nette-forms

A následně načíst a spustit:

import netteForms from 'nette-forms';
netteForms.initOnLoad();

Alternativně jej můžete načíst přímo ze složky vendor:

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

Dynamický JavaScript

Chcete zobrazit políčka pro zadání adresy pouze pokud uživatel zvolí zboží zaslat poštou? Žádný problém. Klíčem je dvojice metod addCondition() & toggle():

$form->addCheckbox('send_it')
	->addCondition($form::EQUAL, true)
		->toggle('#address-container');

Tento kód říká, že když je podmínka splněná, tedy když je zaškrtnutý checkbox, bude viditelný HTML element #address-container. A obráceně. Prvky formuláře s adresou příjemce tak umístíme do kontejneru s tímto ID a při kliknutí na checkbox se skryjí nebo zobrazí. To zajišťuje skript netteForms.js.

Jako argument metody toggle() je možné předat libovolný selektor. Z historických důvodů se alfanumerický řetězec bez dalších speciálních znaků chápe jako ID prvku, tedy stejně, jako by mu předcházel znak #. Druhý nepovinný parametr umožňuje obrátit chování, tj. pokud bychom použili toggle('#address-container', false), element by se naopak zobrazil pouze tehdy, pokud by checkbox zaškrtnutý nebyl.

Výchozí implementace v JavaScriptu mění elementům property hidden. Chování však můžeme snadno změnit, například přidat animaci. Stačí v JavaScriptu přepsat metodu Nette.toggle vlastním řešením:

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// skryjeme nebo zobrazime 'el' dle hodnoty 'visible'
	});
};

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([]); // Nevaliduje vůbec
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Validuje pouze prvek name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Validuje pouze prvek 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.