Űrlap validáció

Kötelező elemek

A kötelező elemeket a setRequired() metódussal jelöljük meg, amelynek argumentuma a hibaüzenetek hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki az elemet. Ha nem adunk meg argumentumot, az alapértelmezett hibaüzenet kerül felhasználásra.

$form->addText('name', 'Név:')
	->setRequired('Kérjük, adja meg a nevét');

Szabályok

Validációs szabályokat az elemekhez az addRule() metódussal adunk hozzá. Az első paraméter a szabály, a második a hibaüzenetek hibaüzenet szövege, a harmadik pedig a validációs szabály argumentuma.

$form->addPassword('password', 'Jelszó:')
	->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8);

A validációs szabályok csak akkor kerülnek ellenőrzésre, ha a felhasználó kitöltötte az elemet.

A Nette számos előre definiált szabállyal rendelkezik, amelyek nevei a Nette\Forms\Form osztály konstansai. Minden elemhez használhatjuk ezeket a szabályokat:

konstans leírás argumentum típusa
Required kötelező elem, alias a setRequired() számára
Filled kötelező elem, alias a setRequired() számára
Blank az elem nem lehet kitöltve
Equal az érték megegyezik a paraméterrel mixed
NotEqual az érték nem egyezik meg a paraméterrel mixed
IsIn az érték megegyezik a tömb valamelyik elemével array
IsNotIn az érték nem egyezik meg a tömb egyik elemével sem array
Valid az elem helyesen van kitöltve? (feltételek számára)

Szöveges bevitelek

Az addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat() elemekhez használhatók a következő szabályok is:

MinLength minimális szöveghossz int
MaxLength maximális szöveghossz int
Length hossz tartományban vagy pontos hossz pár [int, int] vagy int
Email érvényes e-mail cím
URL abszolút URL
Pattern megfelel a reguláris kifejezésnek string
PatternInsensitive mint a Pattern, de kis- és nagybetű érzéketlen string
Integer egész szám érték
Numeric alias az Integer számára
Float szám
Min numerikus elem minimális értéke int|float
Max numerikus elem maximális értéke int|float
Range érték tartományban pár [int|float, int|float]

Az Integer, Numeric és Float validációs szabályok azonnal átalakítják az értéket integerre, illetve floatra. Továbbá az URL szabály elfogadja a séma nélküli címet is (pl. nette.org), és kiegészíti a sémát (https://nette.org). A Pattern és PatternIcase kifejezésnek az egész értékre kell érvényesnek lennie, azaz mintha ^ és $ karakterekkel lenne körbevéve.

Elemek száma

Az addMultiUpload(), addCheckboxList(), addMultiSelect() elemekhez használhatók a következő szabályok is a kiválasztott elemek, illetve feltöltött fájlok számának korlátozására:

MinLength minimális szám int
MaxLength maximális szám int
Length szám tartományban vagy pontos szám pár [int, int] vagy int

Fájlfeltöltések

Az addUpload(), addMultiUpload() elemekhez használhatók a következő szabályok is:

MaxFileSize maximális fájlméret bájtban int
MimeType MIME típus, helyettesítő karakterek engedélyezettek ('video/*') string|string[]
Image JPEG, PNG, GIF, WebP, AVIF kép
Pattern a fájlnév megfelel a reguláris kifejezésnek string
PatternInsensitive mint a Pattern, de kis- és nagybetű érzéketlen string

A MimeType és Image szabályokhoz szükség van a fileinfo PHP kiterjesztésre. Azt, hogy a fájl vagy kép a kívánt típusú-e, az aláírása alapján észlelik, és nem ellenőrzik az egész fájl integritását. Azt, hogy a kép nem sérült-e, például a betöltésével lehet megállapítani.

Hibaüzenetek

Minden előre definiált szabálynak, kivéve a Pattern és PatternInsensitive szabályokat, van alapértelmezett hibaüzenete, így azt el lehet hagyni. Azonban az összes üzenet testreszabott megadásával és megfogalmazásával felhasználóbarátabbá teheti az űrlapot.

Az alapértelmezett üzeneteket megváltoztathatja a konfigurációban, a Nette\Forms\Validator::$messages tömb szövegeinek módosításával, vagy a fordító használatával.

A hibaüzenetek szövegében a következő helyettesítő stringek használhatók:

%d sorban helyettesíti a szabály argumentumaival
%n$d helyettesíti a szabály n-edik argumentumával
%label helyettesíti az elem címkéjével (kettőspont nélkül)
%name helyettesíti az elem nevével (pl. name)
%value helyettesíti a felhasználó által beírt értékkel
$form->addText('name', 'Név:')
	->setRequired('Kérjük, töltse ki a %label mezőt');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'legalább %d és legfeljebb %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'legfeljebb %2$d és legalább %1$d', [5, 10]);

Feltételek

A szabályokon kívül feltételeket is hozzáadhatunk. Ezeket hasonlóan írjuk, mint a szabályokat, csak az addRule() helyett az addCondition() metódust használjuk, és természetesen nem adunk meg hibaüzenetet (a feltétel csak kérdez):

$form->addPassword('password', 'Jelszó:')
	// ha a jelszó nem hosszabb 8 karakternél
	->addCondition($form::MaxLength, 8)
		// akkor számjegyet kell tartalmaznia
		->addRule($form::Pattern, 'Számjegyet kell tartalmaznia', '.*[0-9].*');

A feltételt az aktuális elemen kívül más elemhez is köthetjük az addConditionOn() segítségével. Első paraméterként az elemre való hivatkozást adjuk meg. Ebben a példában az e-mail csak akkor lesz kötelező, ha a checkbox be van jelölve (az értéke true lesz):

$form->addCheckbox('newsletters', 'küldjenek nekem hírleveleket');

$form->addEmail('email', 'E-mail:')
	// ha a checkbox be van jelölve
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// akkor követelje meg az e-mailt
		->setRequired('Adja meg az e-mail címét');

A feltételekből komplex struktúrákat hozhatunk létre az elseCondition() és endCondition() segítségével:

$form->addText(/* ... */)
	->addCondition(/* ... */) // ha az első feltétel teljesül
		->addConditionOn(/* ... */) // és a második feltétel egy másik elemen
			->addRule(/* ... */) // követelje meg ezt a szabályt
		->elseCondition() // ha a második feltétel nem teljesül
			->addRule(/* ... */) // követelje meg ezeket a szabályokat
			->addRule(/* ... */)
		->endCondition() // visszatérünk az első feltételhez
		->addRule(/* ... */);

A Nette-ben nagyon könnyen reagálhatunk a feltétel teljesülésére vagy nem teljesülésére JavaScript oldalon is a toggle() metódus segítségével, lásd dinamikus JavaScript.

Hivatkozás más elemre

Szabály vagy feltétel argumentumaként más űrlap elemet is átadhatunk. A szabály ezután a felhasználó által később a böngészőben beírt értéket használja. Így például dinamikusan validálhatjuk, hogy a password elem ugyanazt a stringet tartalmazza-e, mint a password_confirm elem:

$form->addPassword('password', 'Jelszó');
$form->addPassword('password_confirm', 'Jelszó megerősítése')
    ->addRule($form::Equal, 'A megadott jelszavak nem egyeznek', $form['password']);

Egyéni szabályok és feltételek

Néha olyan helyzetbe kerülünk, amikor a Nette beépített validációs szabályai nem elegendőek, és a felhasználótól származó adatokat a saját módunkon kell validálnunk. A Nette-ben ez nagyon egyszerű!

Az addRule() vagy addCondition() metódusoknak első paraméterként tetszőleges callbacket adhatunk át. Ez első paraméterként magát az elemet kapja, és boolean értéket ad vissza, amely meghatározza, hogy a validáció rendben lezajlott-e. Az addRule() segítségével történő szabály hozzáadásakor további argumentumokat is megadhatunk, ezeket aztán második paraméterként adjuk át.

Így létrehozhatunk egy saját validátor készletet osztályként statikus metódusokkal:

class MyValidators
{
	// teszteli, hogy az érték osztható-e az argumentummal
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// további validátorok
	}
}

A használat ezután nagyon egyszerű:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'Az értéknek a %d szám többszörösének kell lennie',
		8,
	);

Egyéni validációs szabályokat JavaScripthez is hozzáadhatunk. A feltétel az, hogy a szabály statikus metódus legyen. A neve a JavaScript validátor számára az osztály nevének a visszaperjelek \ nélküli, aláhúzásjel _ és a metódus nevének összekapcsolásával jön létre. Pl. az App\MyValidators::validateDivisibility-t AppMyValidators_validateDivisibility-ként írjuk, és hozzáadjuk a Nette.validators objektumhoz:

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

onValidate esemény

Az űrlap elküldése után validáció történik, amely során ellenőrzésre kerülnek az addRule() segítségével hozzáadott egyes szabályok, majd kiváltódik az esemény onValidate. Ennek a kezelőjét (handler) kiegészítő validációra használhatjuk, tipikusan az értékek helyes kombinációjának ellenőrzésére több űrlap elemben.

Ha hibát észlelünk, azt az addError() metódussal adjuk át az űrlapnak. Ezt vagy egy konkrét elemen, vagy közvetlenül az űrlapon hívhatjuk meg.

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('Ez a kombináció nem lehetséges.');
	}
}

Hibák a feldolgozás során

Sok esetben csak akkor értesülünk a hibáról, amikor az érvényes űrlapot dolgozzuk fel, például új elemet írunk az adatbázisba, és kulcsduplikációba ütközünk. Ebben az esetben a hibát ismét az addError() metódussal adjuk át az űrlapnak. Ezt vagy egy konkrét elemen, vagy közvetlenül az űrlapon hívhatjuk meg:

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('Érvénytelen jelszó.');
	}
}

Ha lehetséges, javasoljuk, hogy a hibát közvetlenül az űrlap eleméhez csatolja, mert az alapértelmezett renderer használatakor mellette jelenik meg.

$form['date']->addError('Elnézést, de ez a dátum már foglalt.');

Az addError() metódust ismételten meghívhatja, és így több hibaüzenetet adhat át az űrlapnak vagy elemnek. Ezeket a getErrors() segítségével szerezheti meg.

Figyelem, a $form->getErrors() az összes hibaüzenet összegzését adja vissza, beleértve azokat is, amelyeket közvetlenül az egyes elemekhez adtak át, nem csak közvetlenül az űrlaphoz. A csak az űrlaphoz átadott hibaüzeneteket a $form->getOwnErrors() segítségével szerezheti meg.

Bemenet módosítása

Az addFilter() metódus segítségével módosíthatjuk a felhasználó által beírt értéket. Ebben a példában toleráljuk és eltávolítjuk a szóközöket az irányítószámban:

$form->addText('zip', 'Irányítószám:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // eltávolítjuk a szóközöket az irányítószámból
	})
	->addRule($form::Pattern, 'Az irányítószám nem öt számjegyű', '\d{5}');

A szűrő beépül a validációs szabályok és feltételek közé, tehát a metódusok sorrendje számít, azaz a szűrő és a szabály abban a sorrendben hívódik meg, ahogy az addFilter() és addRule() metódusok sorrendje van.

JavaScript validáció

A feltételek és szabályok megfogalmazásának nyelve nagyon erős. Minden konstrukció működik mind a szerveroldalon, mind a JavaScript oldalon. HTML attribútumokban data-nette-rules JSON formátumban kerülnek átadásra. Magát a validációt pedig egy szkript végzi, amely elfogja az űrlap submit eseményét, végigmegy az egyes elemeken, és végrehajtja a megfelelő validációt.

Ez a szkript a netteForms.js, és több lehetséges forrásból érhető el:

A szkriptet közvetlenül beillesztheti a HTML oldalba egy CDN-ről:

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

Vagy másolja helyileg a projekt nyilvános mappájába (pl. a vendor/nette/forms/src/assets/netteForms.min.js fájlból):

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

Vagy telepítse npm segítségével:

npm install nette-forms

Majd töltse be és futtassa:

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

Alternatívaként betöltheti közvetlenül a vendor mappából:

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

Dinamikus JavaScript

Szeretné megjeleníteni a cím megadására szolgáló mezőket csak akkor, ha a felhasználó postai kézbesítést választ? Semmi probléma. A kulcs az addCondition() & toggle() metóduspár:

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

Ez a kód azt mondja, hogy amikor a feltétel teljesül, tehát amikor a checkbox be van jelölve, a #address-container HTML elem látható lesz. És fordítva. A címzett címét tartalmazó űrlap elemeket tehát egy ilyen ID-jű konténerbe helyezzük, és a checkboxra kattintva elrejtődnek vagy megjelennek. Ezt a netteForms.js szkript biztosítja.

A toggle() metódus argumentumaként tetszőleges selectort adhatunk át. Történelmi okokból az alfanumerikus string további speciális karakterek nélkül elem ID-ként értelmeződik, tehát ugyanúgy, mintha # karakter előzné meg. A második, nem kötelező paraméter lehetővé teszi a viselkedés megfordítását, azaz ha a toggle('#address-container', false)-t használnánk, az elem éppen ellenkezőleg, csak akkor jelenne meg, ha a checkbox nem lenne bejelölve.

Az alapértelmezett implementáció JavaScriptben az elemek hidden propertyjét változtatja meg. A viselkedést azonban könnyen megváltoztathatjuk, például animációt adhatunk hozzá. Elég JavaScriptben felülírni a Nette.toggle metódust saját megoldással:

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// elrejtjük vagy megjelenítjük az 'el'-t a 'visible' értékétől függően
	});
};

Validáció kikapcsolása

Néha hasznos lehet a validáció kikapcsolása. Ha a küldés gomb megnyomása nem kell, hogy validációt végezzen (alkalmas Cancel vagy Preview gombokhoz), kikapcsoljuk a $submit->setValidationScope([]) metódussal. Ha csak részleges validációt kell végeznie, megadhatjuk, mely mezők vagy űrlap konténerek validálódjanak.

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

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

$form->addSubmit('send1'); // Az egész űrlapot validálja
$form->addSubmit('send2')
	->setValidationScope([]); // Egyáltalán nem validál
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Csak a name elemet validálja
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Csak az age elemet validálja
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // A details konténert validálja

A setValidationScope nem befolyásolja a onValidate esemény eseményt az űrlapon, amely mindig meghívásra kerül. A konténer onValidate eseménye csak akkor kerül kiváltásra, ha ez a konténer részleges validációra van megjelölve.

verzió: 4.0