Chcąc się upewnić co do naszej nieomylności nie wystarczy nam czasem sucha odpowiedź twierdząca. Chcemy aby nasz zmysł słuchu przyjemnie łaskotały piękne słówka. Każdy chciałby usłyszeć: "Hej! Kolego, jesteś jedynym na świecie, który ma rację!", albo "Droga Koleżanko! Chylę czoła przed Twoją mądrością!". Czy Constraint Validation API będzie tym, który szepnie parę ładnych słów?
Stary, ale jary.
Walidacja formularzy na poziomie klienta oczywiście nie jest nowością, ale w wielu przypadkach nadal na czasie. W prosty sposób można przeprowadzić użytkownika przez proces wprowadzania danych w taki sposób, aby uzyskać prawidłowe wartości. I to bez angażowania zewnętrznych bibliotek w JavaScript. Wystarczy dla pola wymagalnego dodać atrybut required, a chcąc dodatkowo osiągnąć wartość w określnym formacie czy też zakresie używamy atrybutów: pattern, min / max), minlength / maxlength, czy step.
I jak widać to działa. Oczywiście dalszą walidację należy przeprowadzić po stronie serwerowej, jednak przy małym nakładzie pracy osiągnęliśmy pożądany efekt. No może nie do końca, bo nie każdemu mogą odpowiadać domyślne treści w dymkach. Wyglądu tychże dymków nie możemy zmienić, ale właśnie treści tak. Wykorzystamy do tego wspomniane API, a dokładnie dwie metody: checkValidity oraz setCustomValidity. Pierwsza z nich sprawdza poprawność wartości na podstawie atrybutów walidacji, druga umożliwa wprowadzenie własnej treści do dymka. Wszystko brzmi prosto pod warunkiem, że znajdziemy odpowiedni moment w skrypcie na podpięcie tych metod. Bo czy dobre są:
- obsługa zdarzenia submit formularza? Nie, bo w tym momencie jesteśmy już po natywnej walidacji, więc zobaczymy i tak domyślne treści w dymkach.
- obsługa zdarzenia submit formularza z ustawionym atrybutem novalidate? Nie, bo pomimo możliwości kontroli walidacji poprzez checkValidity i ustawianiu własnej treści, dymek nie wyświetli się,
- obsługa delegowanego zdarzenia blur formularza? Nie, bo to zdarzenie nie puszcza bąbelków.
- obsługa zdarzenia blur pola? Nie, bo być może użytkownik w ogóle nie dotknie takiego pola, więc i to zdarzenie nie zajdzie.
- obsługa delegowanego zdarzenia focusout formularza? Nie, bo choć bąbelkuje, to ma wadę jak zdarzenie blur dla pola.
- obsługa delegowanego zdarzenia invalid formularza? Nie, bo też nie bąbelkuje.
- obsługa zdarzenia invalid dla pola? Nie, bo metoda checkValidity w przypadku braku poprawnej walidacji wywołuje zdarzenie invalid... O ile zobaczymy własny komunikat, to wpadniemy w rekurencyjnego wywołania i otrzymany na końcu błąd: Maximum call stack size exceeded.
Jak walidować premierze, jak walidować?
Oczywiście nie wyczerpaliśmy wszystkich możliwych opcji. Z odsieczą przyjdzie zdarzenie input. Jest o tyle dobrym rozwiązaniem, że możemy delegować je na formularz i tam sprawdzić czy źródłem zdarzenia jest pole, które chcemy walidować.
Wykorzystamy formularz z wpisu Brudny Harry, czyli ochrona formularza przed przeładowaniem i utratą danych, zresztą ten sam jak w przykładzie wyżej, ale delikatnie zmodyfikowany:
<form>
<fieldset>
<label>PHP
<input type="radio" name="radio" value="1" required data-validate="Hej! Wybierz jeden z języków."/></label>
<label>JavaScript<input type="radio" name="radio" value="2"/></label>
<label>Basic<input type="radio" name="radio" value="3"/></label>
</fieldset>
<fieldset>
<input type="text" required data-validate="Podaj imię." /><br />
<textarea required minlength="3" data-validate="Podaj opis (przynajmniej 3 znaki)."></textarea><br />
<select required data-validate="Wybierz jedno z państw.">
<option></option>
<option value="1">Polska</option>
<option value="2">Bułgaria</option>
<option value="3">Słowenia</option>
</select>
</fieldset>
<input type="submit" />
</form>
Do pól, dla których chcemy wyświetlać inną treść niż standardowa, dodaliśmy atrybut data-validate. Wykorzystamy go za moment w kodzie JavaScript. Nie ma tu pól typu checkbox. Dlaczego? O ile w grupie takich pól (od 1 do N) wszystkie są wymagalne, to nie ma problemu. Do każdego dodajemy atrybut required oraz wspomniany atrybut (dla każdego ptaszka możemy dodać inną treść). Jednak gdy nasza walidacja jest bardziej skomplikowana, np. chcemy, aby z 5 pól 3 dowolne były do zaznaczenia, musimy dopisać trochę własnego kodu, który zastąpi nam brak natywnej walidacji dla takiego przypadku.
I obsługa komunikatów w JavaScript:
document.querySelector("form").addEventListener("input", function(event) {
if (event.target.getAttribute("data-validate")) {
event.target.setCustomValidity("");
if (!event.target.checkValidity()) {
event.target.setCustomValidity(event.target.dataset.validate);
}
}
});
document.querySelectorAll("form [data-validate]").forEach(function(el) {
var e = document.createEvent("Event");
e.initEvent("input", true);
el.dispatchEvent(e);
});
document.querySelector('form').addEventListener('click', function () {
document.querySelectorAll('form input[type="radio"]')
.forEach(function (el) {
document.querySelectorAll('form input[type="radio"][name="' + el.name + '"]')
.forEach(function (el) {
el.setCustomValidity("");
});
});
});
Ponieważ jak wspomniałem zdarzenie input bąbelkuje, to wykorzystujemy delegację zdarzeń i podpinamy się pod formularz (pamiętaj, że niektóre przeglądarki nie obsługują dla niektórych pól zdarzenia input - pisałem o tym w Brudny Harry, czyli ochrona formularza przed przeładowaniem i utratą danych (spin-off)). W obsłudze zdarzenia sprawdzamy czy źródłem zdarzenia jest pole z atrybutem data-validate. Wstępnie ustawiamy pusty komunikat błędu (pole poprawne). Następnie sprawdzamy czy pole przeszło walidację, jeśli nie, to zmieniamy treść komunikatu na własną. Może Cię zdziwić dlaczego nie możemy ustawić dla pól odgórnie własnych treści błędów i w ogóle nie wpinać się w zdarzenie, czyli po prostu podmienić te natywne treści raz, na początku kodu. Niestety nie ma takiej możliwości, tzn. oczywiście możemy tak zrobić, ale w takim przypadku nawet kiedy pole będzie poprawnie wypełnione, to dymek również się wyświetli (z naszą treścią), bowiem komunikat ustala pole jako nieprawidłowo zwalidowane (choć zdarzenie invalid przy wywołaniu tej metody nie jest odpalane).
Drugi fragment kodu zapewnia nam to, że przy braku interakcji użytkownika z polem, przy nieprawidłowej walidacji i zatwierdzaniu formularza, otrzymamy naszą treść w komunikacie. Odpalamy tu po prostu zdarzenie delegowane (ten fragment wyżej) - dla przypomnienia: Odłamkowym ładuj! Czyli o odpalaniu zdarzeń delegowanych.
W trzeciej części na kliknięcie jednego radio z grupy czyścimy komunikat walidacji, aby zapewnić, że cała grupa powinna być poprawnie walidowana.
Jeśli chciałbyś tworzyć szczegółowe komunikaty w zależności od tego czy pole jest wypełnione, czy wprowadzono zbyt mało znaków czy wartość jest za duża, itp., wtedy należy dodatkowo użyć właściwości validity dla pola, która posiada następujące (tylko do odczytu) cechy:
- badInput - jeśli wartość nie mogła zostać poddana kolejnemu krokowi walidacji, np. dla pola typu number wprowadzony został tekst,
- customError - jeśli pole posiada komunikat błędu inny niż natywny,
- patternMismatch - jeśli wartość nie zgadza się z wzorcem z atrybutu pattern,
- rangeOverflow - jeśli wartość jest wyższa niż ustalona w atrybucie max,
- rangeUnderflow - jeśli wartość jest niższa niż ustalona w atrybucie min,
- stepMismatch - jeśli wartość nie jest wielokrotnością atrybutu step,
- tooLong - jeśli ilość znaków przekracza wartość ustaloną w atrybucie maxlength,
- tooShort - jeśli ilość znaków przekracza wartość ustaloną w atrybucie minlength,
- typeMismatch - jeśli wartość nie zgadza się z typem pola, np. dla typu url podano nieprawidłowy adres,
- valid - jeśli poprawnie przeszło walidację,
- valueMissing - jeśli brak wartości, a ustawiony jest atrybut required.
Bardzo się cieszę, że udało Ci się dotrwać do końca. Jesteś taki mądry! Jesteś taka mądra!
Przydatne linki:
Odłamkowym ładuj! Czyli o odpalaniu zdarzeń delegowanych.
Brudny Harry, czyli ochrona formularza przed przeładowaniem i utratą danych (spin-off)
Brudny Harry, czyli ochrona formularza przed przeładowaniem i utratą danych.
Constraint Validation API.
Constraint Validation API - przykłady.