Validacija obrazcev

Obvezni elementi

Obvezne elemente označimo z metodo setRequired(), katere argument je besedilo sporočila o napakah, ki se prikaže, če uporabnik elementa ne izpolni. Če argumenta ne navedemo, se uporabi privzeto sporočilo o napaki.

$form->addText('name', 'Ime:')
	->setRequired('Prosimo, vnesite ime');

Pravila

Validacijska pravila dodajamo elementom z metodo addRule(). Prvi parameter je pravilo, drugi je besedilo sporočila o napakah in tretji je argument validacijskega pravila.

$form->addPassword('password', 'Geslo:')
	->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8);

Validacijska pravila se preverjajo samo v primeru, da je uporabnik element izpolnil.

Nette prihaja s celo vrsto preddefiniranih pravil, katerih imena so konstante razreda Nette\Forms\Form. Pri vseh elementih lahko uporabimo ta pravila:

konstanta opis tip argumenta
Required obvezen element, alias za setRequired()
Filled obvezen element, alias za setRequired()
Blank element ne sme biti izpolnjen
Equal vrednost je enaka parametru mixed
NotEqual vrednost ni enaka parametru mixed
IsIn vrednost je enaka nekateremu elementu v polju array
IsNotIn vrednost ni enaka nobenemu elementu v polju array
Valid je element pravilno izpolnjen? (za pogoje)

Besedilni vnosi

Pri elementih addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat() lahko uporabimo tudi nekatera naslednja pravila:

MinLength minimalna dolžina besedila int
MaxLength maksimalna dolžina besedila int
Length dolžina v obsegu ali natančna dolžina par [int, int] ali int
Email veljaven e-poštni naslov
URL absolutni URL
Pattern ustreza regularnemu izrazu string
PatternInsensitive kot Pattern, vendar neodvisno od velikosti črk string
Integer celoštevilska vrednost
Numeric alias za Integer
Float število
Min minimalna vrednost numeričnega elementa int|float
Max maksimalna vrednost numeričnega elementa int|float
Range vrednost v obsegu par [int|float, int|float]

Validacijska pravila Integer, Numeric in Float takoj pretvorijo vrednost v integer oz. float. In nadalje pravilo URL sprejme tudi naslov brez sheme (npr. nette.org) in shemo dopolni (https://nette.org). Izraz v Pattern in PatternIcase mora veljati za celotno vrednost, tj. kot da bi bil obdan z znakoma ^ in $.

Število elementov

Pri elementih addMultiUpload(), addCheckboxList(), addMultiSelect() lahko uporabimo tudi naslednja pravila za omejitev števila izbranih elementov oz. naloženih datotek:

MinLength minimalno število int
MaxLength maksimalno število int
Length število v obsegu ali natančno število par [int, int] ali int

Nalaganje datotek

Pri elementih addUpload(), addMultiUpload() lahko uporabimo tudi naslednja pravila:

MaxFileSize maksimalna velikost datoteke v bajtih int
MimeType MIME tip, dovoljeni nadomestni znaki ('video/*') string|string[]
Image slika JPEG, PNG, GIF, WebP, AVIF
Pattern ime datoteke ustreza regularnemu izrazu string
PatternInsensitive kot Pattern, vendar neodvisno od velikosti črk string

MimeType in Image zahtevata PHP razširitev fileinfo. Da je datoteka ali slika zahtevanega tipa, zaznajo na podlagi njene signature in ne preverjajo integritete celotne datoteke. Ali slika ni poškodovana, lahko ugotovite na primer s poskusom njenega nalaganjem.

Sporočila o napakah

Vsa preddefinirana pravila z izjemo Pattern in PatternInsensitive imajo privzeto sporočilo o napaki, zato ga lahko izpustite. Vendar z navedbo in oblikovanjem vseh sporočil po meri naredite obrazec uporabniku prijaznejši.

Spremeniti privzeta sporočila lahko v konfiguraciji, s prilagoditvijo besedil v polju Nette\Forms\Validator::$messages ali z uporabo prevajalniku.

V besedilu sporočil o napakah lahko uporabljate te nadomestne nize:

%d postopoma nadomesti z argumenti pravila
%n$d nadomesti z n-tim argumentom pravila
%label nadomesti z oznako elementa (brez dvopičja)
%name nadomesti z imenom elementa (npr. name)
%value nadomesti z vrednostjo, ki jo je vnesel uporabnik
$form->addText('name', 'Ime:')
	->setRequired('Izpolnite prosim %label');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'najmanj %d in največ %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'največ %2$d in najmanj %1$d', [5, 10]);

Pogoji

Poleg pravil lahko dodajamo tudi pogoje. Ti se zapisujejo podobno kot pravila, le da namesto addRule() uporabimo metodo addCondition() in seveda ne navajamo nobenega sporočila o napaki (pogoj se samo sprašuje):

$form->addPassword('password', 'Geslo:')
	// če geslo ni daljše od 8 znakov
	->addCondition($form::MaxLength, 8)
		// potem mora vsebovati števko
		->addRule($form::Pattern, 'Mora vsebovati števko', '.*[0-9].*');

Pogoj je mogoče vezati tudi na drug element kot trenutni s pomočjo addConditionOn(). Kot prvi parameter navedemo sklic na element. V tem primeru bo e-pošta obvezna le takrat, ko se označi potrditveno polje (njegova vrednost bo true):

$form->addCheckbox('newsletters', 'pošiljajte mi novice');

$form->addEmail('email', 'E-pošta:')
	// če je potrditveno polje označeno
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// potem zahtevaj e-pošto
		->setRequired('Vnesite e-poštni naslov');

Iz pogojev je mogoče ustvarjati kompleksne strukture s pomočjo elseCondition() in endCondition():

$form->addText(/* ... */)
	->addCondition(/* ... */) // če je izpolnjen prvi pogoj
		->addConditionOn(/* ... */) // in drugi pogoj na drugem elementu
			->addRule(/* ... */) // zahtevaj to pravilo
		->elseCondition() // če drugi pogoj ni izpolnjen
			->addRule(/* ... */) // zahtevaj ta pravila
			->addRule(/* ... */)
		->endCondition() // vračamo se k prvemu pogoju
		->addRule(/* ... */);

V Nette je mogoče zelo enostavno reagirati na izpolnitev ali neizpolnitev pogoja tudi na strani JavaScripta s pomočjo metode toggle(), glej dynamický JavaScript.

Sklic na drug element

Kot argument pravila ali pogoja lahko predamo tudi drug element obrazca. Pravilo potem uporabi vrednost, ki jo je kasneje vnesel uporabnik v brskalniku. Tako lahko npr. dinamično validiramo, da element password vsebuje enak niz kot element password_confirm:

$form->addPassword('password', 'Geslo');
$form->addPassword('password_confirm', 'Potrdite geslo')
    ->addRule($form::Equal, 'Vneseni gesli se ne ujemata', $form['password']);

Pravila in pogoji po meri

Včasih se znajdemo v situaciji, ko nam vgrajena validacijska pravila v Nette ne zadostujejo in moramo podatke od uporabnika validirati po svoje. V Nette je to zelo enostavno!

Metodam addRule() ali addCondition() lahko kot prvi parameter predamo poljuben povratni klic. Ta sprejme kot prvi parameter sam element in vrača boolean vrednost, ki določa, ali je validacija potekala v redu. Pri dodajanju pravila s pomočjo addRule() je mogoče vnesti tudi druge argumente, ti so nato predani kot drugi parameter.

Lasten nabor validatorjev tako lahko ustvarimo kot razred s statičnimi metodami:

class MyValidators
{
	// testira, ali je vrednost deljiva z argumentom
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// drugi validatorji
	}
}

Uporaba je nato zelo enostavna:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'Vrednost mora biti večkratnik števila %d',
		8,
	);

Lastna validacijska pravila lahko dodajamo tudi v JavaScript. Pogoj je, da je pravilo statična metoda. Njeno ime za JavaScript validator nastane s spojitvijo imena razreda brez povratnih poševnic \, podčrtaja _ in imena metode. Npr. App\MyValidators::validateDivisibility zapišemo kot AppMyValidators_validateDivisibility in dodamo v objekt Nette.validators:

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

Dogodek onValidate

Po pošiljanju obrazca se izvede validacija, kjer se preverijo posamezna pravila, dodana s pomočjo addRule(), in nato se sproži dogodek onValidate. Njegov obravnavalnik lahko uporabimo za dodatno validacijo, tipično preverjanje pravilne kombinacije vrednosti v več elementih obrazca.

Če se odkrije napaka, jo predamo v obrazec z metodo addError(). To lahko pokličemo bodisi na konkretnem elementu ali neposredno na obrazcu.

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('Ta kombinacija ni mogoča.');
	}
}

Napake pri obdelavi

V mnogih primerih se o napaki zavemo šele v trenutku, ko obdelujemo veljaven obrazec, na primer zapisujemo novo postavko v bazo podatkov in naletimo na podvojitev ključev. V takem primeru napako spet predamo v obrazec z metodo addError(). To lahko pokličemo bodisi na konkretnem elementu ali neposredno na obrazcu:

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

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
		$form->addError('Neveljavno geslo.');
	}
}

Če je mogoče, priporočamo, da napako priključite neposredno elementu obrazca, ker se bo prikazala poleg njega pri uporabi privzetega rendererja.

$form['date']->addError('Oprostite, ampak ta datum je že zaseden.');

Lahko addError() kličete večkrat in tako predaste obrazcu ali elementu več sporočil o napakah. Dobite jih s pomočjo getErrors().

Pozor, $form->getErrors() vrača povzetek vseh sporočil o napakah, tudi tistih, ki so bila predana neposredno posameznim elementom, ne le neposredno obrazcu. Sporočila o napakah, predana samo obrazcu, dobite preko $form->getOwnErrors().

Spreminjanje vnosa

S pomočjo metode addFilter() lahko spremenimo vrednost, ki jo je vnesel uporabnik. V tem primeru bomo tolerirali in odstranjevali presledke v poštni številki:

$form->addText('zip', 'Poštna št.:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // odstranimo presledke iz poštne številke
	})
	->addRule($form::Pattern, 'Poštna št. ni v obliki petih števk', '\d{5}');

Filter se vključi med validacijska pravila in pogoje, zato je vrstni red metod pomemben, tj. filter in pravilo se kličeta v takem vrstnem redu, kot je vrstni red metod addFilter() in addRule().

Validacija JavaScript

Jezik za oblikovanje pogojev in pravil je zelo močan. Vse konstrukcije pri tem delujejo tako na strani strežnika kot tudi na strani JavaScripta. Prenašajo se v HTML atributih data-nette-rules kot JSON. Samo validacijo nato izvaja skript, ki prestreže dogodek obrazca submit, pregleda posamezne elemente in izvede ustrezno validacijo.

Ta skript je netteForms.js in je na voljo iz več možnih virov:

Skript lahko vstavite neposredno v HTML stran iz CDN:

<script src="https://unpkg.com/nette-forms@3"></script>

Ali ga kopirate lokalno v javno mapo projekta (npr. iz vendor/nette/forms/src/assets/netteForms.min.js):

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

Ali namestite preko npm:

npm install nette-forms

In nato naložite in zaženete:

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

Alternativno ga lahko naložite neposredno iz mape vendor:

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

Dinamični JavaScript

Želite prikazati polja za vnos naslova samo, če uporabnik izbere pošiljanje blaga po pošti? Ni problema. Ključ je par metod addCondition() & toggle():

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

Ta koda pravi, da ko je pogoj izpolnjen, torej ko je potrditveno polje označeno, bo viden HTML element #address-container. In obratno. Elemente obrazca z naslovom prejemnika tako postavimo v vsebnik s tem ID-jem in ob kliku na potrditveno polje se skrijejo ali prikažejo. To zagotavlja skript netteForms.js.

Kot argument metode toggle() je mogoče predati poljuben selektor. Iz zgodovinskih razlogov se alfanumerični niz brez drugih posebnih znakov razume kot ID elementa, torej enako, kot če bi mu predhajal znak #. Drugi neobvezni parameter omogoča obrniti vedenje, tj. če bi uporabili toggle('#address-container', false), bi se element nasprotno prikazal samo takrat, če potrditveno polje ne bi bilo označeno.

Privzeta implementacija v JavaScriptu spreminja elementom lastnost hidden. Vedenje pa lahko enostavno spremenimo, na primer dodamo animacijo. Dovolj je, da v JavaScriptu prepišemo metodo Nette.toggle z lastno rešitvijo:

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// skrijemo ali prikažemo 'el' glede na vrednost 'visible'
	});
};

Izklop validacije

Včasih se lahko zgodi, da je treba validacijo izklopiti. Če pritisk na gumb za pošiljanje ne sme izvajati validacije (primerno za gumbe Prekliči ali Predogled), jo izklopimo z metodo $submit->setValidationScope([]). Če naj izvaja le delno validacijo, lahko določimo, katera polja ali vsebnikov obrazca se naj validirajo.

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('age');
$details->addInteger('age2')
	->setRequired('age2');

$form->addSubmit('send1'); // Validira celoten obrazec
$form->addSubmit('send2')
	->setValidationScope([]); // Sploh ne validira
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Validira samo element name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Validira samo element age
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Validira vsebnik details

setValidationScope ne vpliva na dogodek onValidate pri obrazcu, ki bo vedno poklican. Dogodek onValidate pri vsebniku bo sprožen samo, če je ta vsebnik označen za delno validacijo.

različica: 4.0