Zatwierdzanie formularzy

Wymagane elementy

Elementy obowiązkowe oznaczane są metodą setRequired(), której argumentem jest tekst komunikatu o błędzie, który zostanie wyświetlony, jeśli użytkownik nie wypełni elementu. Jeśli nie podano żadnego argumentu, używany jest domyślny komunikat o błędzie.

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

Zasady

Reguły walidacyjne są dodawane do elementów za pomocą metody addRule(). Pierwszym parametrem jest reguła, drugim tekst komunikatu o błędzie, a trzecim argument reguły walidacyjnej.

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

**Reguły walidacji są sprawdzane tylko wtedy, gdy użytkownik wypełnił dany element.

Nette posiada szereg predefiniowanych reguł, których nazwy są stałymi klasy Nette\Forms\Form. Możemy zastosować te reguły do wszystkich elementów:

stały opis typ argumentu
Required element obowiązkowy, alias dla setRequired() -.
Filled element obowiązkowy, alias dla setRequired() -.
Blank element nie może być wypełniony -.
Equal wartość równa się parametrowi mixed
NotEqual wartość nie jest równa parametrowi mixed
IsIn wartość jest równa jakiemuś elementowi w polu . array
IsNotIn wartość nie jest równa żadnej pozycji w tablicy . array
Valid czy element został wypełniony poprawnie? (dla warunków)

Wejścia tekstowe

W przypadku elementów addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat() można również zastosować niektóre z poniższych reguł:

MinLength minimalna długość tekstu int
MaxLength maksymalna długość tekstu int
Length długość zakresu lub dokładna długość para [int, int] lub int
Email ważny adres e-mail
URL bezwzględny URL -.
Pattern pasuje do wyrażenia regularnego string
PatternInsensitive jak Pattern, ale z rozróżnianiem wielkości liter string
Integer wartość całkowita
Numeric alias dla Integer -.
Float liczba
Min minimalna wartość elementu numerycznego int|float
Max maksymalna wartość elementu numerycznego int|float
Range wartość w zakresie para [int|float, int|float]

Reguły walidacji Integer, Numeric i Float bezpośrednio konwertują wartość na liczbę całkowitą i float odpowiednio. Ponadto reguła URL przyjmuje adres bez schematu (np. nette.org) i dodaje schemat (https://nette.org). Wyrażenie w Pattern i PatternIcase musi być ważne dla całej wartości, czyli tak jakby było zawinięte przez ^ a $.

Liczba elementów

W przypadku elementów addMultiUpload(), addCheckboxList(), addMultiSelect() można również użyć następujących reguł, aby ograniczyć liczbę wybranych elementów lub przesłanych plików:

MinLength minimalna liczba int
MaxLength maksymalna liczba int
Length liczba w zakresie lub dokładna liczba pary [int, int] lub int

Przesyłanie plików

Poniższe zasady mogą być również stosowane dla elementów addUpload(), addMultiUpload():

MaxFileSize maksymalny rozmiar pliku w bajtach int
MimeType Typ MIME, dozwolone symbole wieloznaczne ('video/*') string|string[]
Image obraz JPEG, PNG, GIF, WebP -.
Pattern nazwa pliku pasuje do wyrażenia regularnego string
PatternInsensitive jak Pattern, ale z rozróżnianiem wielkości liter string

MimeType i Image wymagają rozszerzenia PHP fileinfo. Wykrywają one, że plik lub obraz jest pożądanego typu na podstawie jego sygnatury i nie sprawdzają integralności całego pliku. Możesz powiedzieć, czy obraz jest uszkodzony, próbując go załadować, na przykład.

Komunikaty o błędach

Wszystkie predefiniowane reguły oprócz Pattern i PatternInsensitive mają domyślny komunikat o błędzie, więc można go pominąć. Jednak wymieniając i formułując wszystkie wiadomości w niestandardowy sposób, sprawisz, że formularz będzie bardziej przyjazny dla użytkownika.

Domyślne komunikaty można zmienić w konfiguracji, edytując tekst w polu Nette\Forms\Validator::$messages lub korzystając z translatora.

W tekście komunikatów o błędach można stosować następujące łańcuchy zastępcze:

%d zastępuje kolejno argumenty reguły
%n$d zastąpić po n-tym argumencie reguły
%label replace po etykiecie elementu (bez dwukropka)
%name zastąpić po nazwie elementu (np. name)
%value zastąpić po wartości wprowadzonej przez użytkownika
$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]);

Warunki

Oprócz reguł, można również dodać warunki. Pisze się je podobnie jak reguły, ale zamiast addRule() używamy metody addCondition() i oczywiście nie dołączamy żadnego komunikatu o błędzie (warunek tylko pyta):

$form->addPassword('password', 'Hasło:')
	// jeśli hasło nie jest dłuższe niż 8 znaków
	->addCondition($form::MaxLength, 8)
		// wtedy musi zawierać cyfrę
		->addRule($form::Pattern, 'Musi zawierać cyfrę', '.*[0-9].*');

Możesz również związać warunek z elementem innym niż bieżący, używając addConditionOn(). Pierwszy parametr jest referencją do elementu. W tym przykładzie e-mail będzie obowiązkowy tylko wtedy, gdy pole wyboru jest zaznaczone (jego wartość będzie prawdziwa):

$form->addCheckbox('newsletters', 'send me newsletters');

$form->addEmail('email', 'Email:')
	// jeśli pole wyboru jest zaznaczone
	->addConditionOn($form['newsletters']], $form::Equal, true)
		// następnie zażądać e-maila
		->setRequired('Wpisz adres e-mail');

Złożone struktury mogą być tworzone z warunków przy użyciu elseCondition() i endCondition():

$form->addText(/* ... */)
	->addCondition(/* ... */) // jeśli pierwszy warunek jest spełniony
		->addConditionOn(/* ... */) // i drugi warunek na innym elemencie
			->addRule(/* ... */) // wymagaj tej reguły
		->elseCondition() // jeśli drugi warunek nie jest spełniony
			->addRule(/* ... */) // wymagaj tych reguł
			->addRule(/* ... */)
		->endCondition() // powrót do pierwszego warunku
		->addRule(/* ... */);

W Nett bardzo łatwo jest również reagować na spełnienie lub niespełnienie warunku po stronie JavaScript za pomocą metody toggle(), patrz dynamiczny JavaScript.

Odniesienia między elementami

Odwołanie do innego elementu może być podane jako argument do reguły lub warunku. Tak więc, na przykład, możliwe jest dynamiczne sprawdzenie, czy element text ma tyle znaków, ile wynosi wartość elementu length:

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

Niestandardowe zasady i warunki

Od czasu do czasu trafiamy na sytuacje, w których wbudowane w Nette reguły walidacji nie są wystarczające i musimy walidować dane użytkownika na swój własny sposób. W Nette jest to bardzo proste!

Jako pierwszy parametr możesz przekazać dowolne wywołanie zwrotne do metod addRule() lub addCondition(). Akceptuje sam element jako pierwszy parametr i zwraca wartość boolean wskazującą, czy walidacja zakończyła się sukcesem. Podczas dodawania reguły za pomocą addRule() można przekazać dodatkowe argumenty, które są wtedy przekazywane jako drugi parametr.

Możemy więc stworzyć własny zestaw walidatorów jako klasę z metodami statycznymi:

class MyValidators
{
	// sprawdza, czy wartość jest podzielna przez argument
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// dodatkowe walidatory
	}
}

Użytkowanie jest wtedy bardzo proste:

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

Niestandardowe reguły walidacji mogą być dodane do JavaScript. Jedynym warunkiem jest to, że reguła musi być metodą statyczną. Jego nazwa dla walidatora JavaScript jest tworzona przez konkatenację nazwy klasy bez backslashes \, podtržítka _ i nazwy metody. Na przykład, App\MyValidators::validateDivisibility jest zapisany jako AppMyValidators_validateDivisibility i dodany do obiektu Nette.validators:

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

Zdarzenie OnValidate

Po przesłaniu formularza, walidacja odbywa się poprzez sprawdzenie poszczególnych reguł dodanych przez addRule(), a następnie wywołanie zdarzenia onValidate. Jego handler może być użyty do dodatkowej walidacji, zazwyczaj do sprawdzenia poprawnej kombinacji wartości w wielu elementach formularza.

Jeśli zostanie wykryty błąd, jest on przekazywany do formularza za pomocą metody addError(). Można to wywołać na konkretnym elemencie lub bezpośrednio na formularzu.

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

Błędy w przetwarzaniu

W wielu przypadkach o błędzie dowiadujemy się dopiero w trakcie przetwarzania poprawnego formularza, np. wprowadzamy nową pozycję do bazy danych i napotykamy na zduplikowany klucz. W tym przypadku błąd jest ponownie przekazywany do formularza za pomocą metody addError(). Można to wywołać zarówno na konkretnym elemencie, jak i bezpośrednio na formularzu:

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Strona główna:');

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

Jeśli to możliwe, zalecamy dołączenie błędu bezpośrednio do elementu formularza, ponieważ pojawi się on obok niego podczas korzystania z domyślnego renderera.

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

Możesz wywołać addError() wielokrotnie, aby przekazać wiele komunikatów o błędach do formularza lub elementu. Można je uzyskać korzystając ze strony getErrors().

Zauważ, że $form->getErrors() zwraca podsumowanie wszystkich komunikatów o błędach, nawet tych, które zostały przekazane bezpośrednio do poszczególnych elementów, a nie tylko bezpośrednio do formularza. Komunikaty o błędach przekazywane tylko do formularza są pobierane przez $form->getOwnErrors().

Wprowadzanie danych

Za pomocą metody addFilter() możemy zmodyfikować wartość wprowadzoną przez użytkownika. W tym przykładzie będziemy tolerować i usuwać spacje w kodzie pocztowym:

$form->addText('zip', 'zip:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // usuń spacje z kodu pocztowego
	})
	->addRule($form::Pattern, 'ZIP code is not in five '\d{5}');

Filtr jest zawarty pomiędzy regułami walidacji i warunkami, a więc zależy od kolejności metod, tzn. filtr i reguła są wywoływane w kolejności metod addFilter() i addRule().

Walidacja w JavaScript

Język do formułowania warunków i zasad jest bardzo potężny. Wszystkie konstrukcje działają zarówno po stronie serwera, jak i po stronie JavaScript. Jest on przekazywany jako JSON w atrybutach HTML strony data-nette-rules. Sama walidacja jest następnie wykonywana przez skrypt, który przechwytuje zdarzenie formularza submit, przechodzi przez elementy i wykonuje odpowiednią walidację.

Ten skrypt to netteForms.js i jest dostępny z wielu możliwych źródeł:

Możesz osadzić skrypt bezpośrednio na stronie HTML z CDN:

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

Albo skopiuj go lokalnie do publicznego folderu projektu (np. z vendor/nette/forms/src/assets/netteForms.min.js):

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

Lub zainstalować za pośrednictwem npm:

npm install nette-forms

A potem załadować i uruchomić:

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

Alternatywnie można załadować go bezpośrednio z folderu vendor:

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

Dynamiczny JavaScript

Czy chcesz pokazać pola wprowadzania adresu tylko wtedy, gdy użytkownik wybierze wysyłkę towarów pocztą? Nie ma problemu. Kluczem jest para metod addCondition() & toggle():

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

Ten kod mówi, że gdy warunek zostanie spełniony, czyli gdy pole wyboru zostanie zaznaczone, element HTML #address-container będzie widoczny. I na odwrót. Umieszczamy więc elementy formularza z adresem odbiorcy w kontenerze o tym ID, a po kliknięciu pola wyboru są one ukrywane lub pokazywane. Zajmuje się tym skrypt netteForms.js.

Jako argument do metody toggle() można przekazać dowolny selektor. Ze względów historycznych ciąg alfanumeryczny bez żadnych innych znaków specjalnych jest traktowany jako identyfikator elementu, czyli tak samo, jakby był poprzedzony znakiem #. Druhý nepovinný parametr umožňuje obrátit chování, tj. pokud bychom použili toggle('#address-container', false), element byłby wyświetlany tylko wtedy, gdy pole wyboru byłoby odznaczone.

Domyślna implementacja JavaScript zmienia właściwość hidden dla elementów. Możemy jednak łatwo zmienić zachowanie, na przykład dodając animację. Wystarczy nadpisać metodę Nette.toggle w JavaScript z niestandardowym rozwiązaniem:

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

Wyłączenie walidacji

Czasami przydatne może być wyłączenie walidacji. Jeśli naciśnięcie przycisku submit nie ma wykonywać walidacji (odpowiednie dla przycisków Cancel lub Preview), wyłączamy go za pomocą metody $submit->setValidationScope([]) Jeśli ma on wykonywać tylko częściową walidację, możemy określić, które pola lub kontenery formularza mają być walidowane.

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

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

$form->addSubmit('send1'); // Waliduje cały formularz
$form->addSubmit('send2')
	->setValidationScope([]); // Nie przeprowadza walidacji w ogóle
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Waliduje tylko element name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Sprawdzanie poprawności tylko elementu "age".
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Waliduje pojemnik z danymi szczegółowymi

setValidationScope Nie wpłynie to na zdarzenie onValidate na formularzu, które zawsze będzie wywoływane. Zdarzenie onValidate na kontenerze zostanie odpalone tylko wtedy, gdy ten kontener zostanie oznaczony do częściowej walidacji.

wersja: 4.0