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);
Validační pravidla se ověřují pouze v případě, že uživatel prvek vyplnil.
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) | – |
Textové vstupy
U prvků addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
, lze použít i některá 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; od nette/forms v2.4.9 |
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í
(http://nette.org
). Výraz v PATTERN
a PATTERN_ICASE
musí platit pro celou hodnotu, tj.
jako by byl obalen znaky ^
a $
.
Počet položek
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 |
Upload souborů
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; od nette/forms v2.4.9 | string |
PATTERN_ICASE |
jako PATTERN , ale nezávislé na velikosti písmen; od nette/forms v2.4.9 |
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í.
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(/* ... */);
Podmínky je možné negovat znakem ~
(vlnovka), tj. addCondition(~$form::NUMBER, ...)
, což je
v Nette 3 zavržené, takže je potřeba použít else-podmínku.
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)
{
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 = new Form;
// ...
$form->onValidate[] = [$this, 'validateSignInForm'];
return $form;
}
public function validateSignInForm(Form $form, \stdClass $data)
{
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\IAuthenticator::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/2/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.