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.
Odniesienie do innego elementu
Jako argument reguły lub warunku można również przekazać inny element formularza. Reguła użyje wtedy wartości
wprowadzonej później przez użytkownika w przeglądarce. Można to wykorzystać na przykład do dynamicznego sprawdzenia, czy
element password
zawiera ten sam ciąg znaków, co element password_confirm
:
$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Confirm Password')
->addRule($form::Equal, 'The passwords do not match', $form['password']);
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"></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.