Formularze - Constraint validation API
Walidacja w czystym HTML
Od kilku lat w HTML5 dostępna jest wbudowana walidacja, która swoje działanie opiera o zastosowanie odpowiednich typów pól formularza i atrybutów dla nich.
Jest to bardzo ciekawe pomysł, który pozwala wprowadzać walidację bez pisania kawałka kodu. Dzięki odpowiedniemu stworzeniu kodu formularza tylko zyskujemy. Dostajemy "darmową" walidację, która działa nawet, gdy użytkownik ma wyłączony Javascript. Po drugie dzięki zastosowaniu odpowiednich typów pól (np. type="email"
dla pola z emailem, type="number"
dla liczb) zyskujemy dodatkowe poprawienie użyteczności naszego formularza, bo np. na urządzeniach mobilnych wyświetlane są odpowiednie klawiatury. Same plusy.
Listę dostępnych typów pól najlepiej zobaczyć na stronie https://developer.mozilla.org/pl/docs/Web/HTML/Element/Input. Warto mieć jednak na uwadze, że nowe rodzaje pól nie w każdej przeglądarce będą się wyświetlać tak samo.
Jeżeli chodzi o atrybuty, to mamy kilka do wykorzystania:
required | określa czy dane pole ma być wypełnione |
---|---|
minlength, maxlength | atrybuty określające minimalna i maksymalną długość wpisywanego tekstu |
min, max | określa minimalną i maksymalną liczbę w polach numerycznych lub dla pól range |
type | określa typ pola. Niektóre z pól mają swoją własną domyślną walidację. I tak np. pola typu email wymagają wpisania emaila, a pola url wpisania odpowiedniego adresu url |
pattern | pozwalają nam podać własny wzór (w formacie regexp) który będzie używany do testu poprawności pola |
Spróbujmy to wykorzystać z prostym formularzu kontaktowym:
<form class="form" id="formTest1" method="post">
<div class="form-row">
<label for="name" class="form-label">Imię*</label>
<input type="text" class="form-control" name="name" id="name" pattern=".{3,}" required>
</div>
<div class="form-row">
<label for="email" class="form-label">Email*</label>
<input type="email" class="form-control" name="email" id="email" required>
</div>
<div class="form-row">
<label for="message" class="form-label">Wiadomość*</label>
<textarea class="form-control" name="message" id="message" required></textarea>
</div>
<div class="form-row">
<button type="submit" class="button form-submit">
Wyślij
</button>
</div>
</form>
Spróbuj teraz wysłać taki formularz. Zobaczysz, że przeglądarka sama w sobie będzie pokazywać odpowiedni komunikat z błędem danego pola.
Dymki z podpowiedzią pokazują się dopiero w drugim kroku, czyli przy próbie wysłania formularza. Jeżeli chcielibyśmy do naszego formularza dodać dynamiczną podpowiedź w czasie wpisywania, możemy to zrobić za pomocą CSS oraz odpowiedni pseudoklas:
:valid | czy pole jest poprawnie wypełnione |
---|---|
:invalid | czy pole jest źle wypełnione |
:user-valid | czy pole jest poprawnie wypełnione, ale po pierwszej interakcji użytkownika z polem |
:user-invalid | czy pole jest źle wypełnione, ale po pierwszej interakcji użytkownika z polem |
np:
input:user-invalid {
border-color: red;
outline: none;
box-shadow: 0 0 2px red;
}
input:user-valid {
border-color: #4dcc23;
outline: none;
box-shadow: 0 0 2px #4dcc23;
}
<input type="text" name="numbers" required pattern="[0-9]{3,}">
W powyższym kodzie zastosowałem selektory :user-valid
i :user-invalid
(https://developer.chrome.com/blog/css-wrapped-2023#user-states). Jak zauważysz, ich działanie rozpoczyna się w momencie, gdy użytkownik opuści wypełniane pole (zdarzenie :blur). Są to nowe selektory, dlatego zastanów się, czy aby na pewno będą dobrym wyborem dla twojego projektu (jakie przeglądarki wspiera twój projekt).
Do tej pory przy takiej walidacji mogliśmy korzystać z selektorów :valid
i :invalid
. Pokazywały one błędy jeszcze przed interakcją użytkownika z polem. Oznaczało to, że użytkownik po wejściu na stronę od razu widział błędnie wypełnione pola. Mogliśmy do obejść np. poprzez dodanie do pola placeholdera np. ze spacją i użycia selektora :placeholder-shown
:
input:invalid { background: red; }
input::placeholder-shown { background: white; }
<input type="text" name="name" required pattern=".{3,}" placeholder=" ">
Constrains Validation API
Javascript udostępnia nam specjalny interfejs - zwany Constrains Validation API, który bardzo ułatwia walidację formularzy, a który sprzężony jest z powyższym mechanizmem z HTML.
Udostępnia on nam kilka metod i właściwości:
Metody:
input.checkValidity() | sprawdza czy pole jest poprawnie wypełnione. Zwraca true/false, a jeżeli pole jest błędnie wypełnione, odpala dodatkowo event invalid. |
---|---|
form.reportValidity() | Wykonuje sprawdzenie takie jak przy próbie wysłania formularza, który ma walidację w HTML, po czym zwraca true/false. Dla użytkownika oznacza to, że przy pierwszym błędnie wypełnionym polu pojawi się domyślny dymek z komunikatem błędu. |
input.setCustomValidity(message) | Pozwala ustawić własny komunikat o błędzie, który pojawia się przy walidacji danego pola |
Właściwości:
validity | zawiera obiekt ValidityState , który jest zbiorem właściwości na temat stanu walidacji danego pola (opisane poniżej) |
---|---|
validationMessage | Jeżeli pole podlega walidacji (posiada atrybut required), właściwość ta zawiera aktualną wiadomość, która pojawi się w dymku podczas walidacji. Dla niektórych pól (email, url) wiadomość taka może się zmienić podczas pisania |
willValidate | Zwraca true/false w zależności od tego, czy pole będzie sprawdzane |
Własna walidacja
Powyżej w czystym HTML za pomocą atrybutów i pól dodaliśmy do naszego formularza walidację.
Ten sam mechanizm możemy wykorzystać w Javascript, dzięki czemu unikniemy pisania dodatkowych testów dla danych pól.
Zanim jednak to zrobimy, powinniśmy wyłączyć domyślne HTMLowe dymki poprzez dodanie do formularza atrybutu novalidate
:
const form = document.querySelector("form");
form.setAttribute("novalidate", true);
Robimy to specjalnie za pomocą Javascript. Dzięki temu osoba z wyłączonym Javascript dostanie domyślną "brzydką" walidację. Jeżeli Javascript zadziała, wtedy użytkownik będzie mógł się cieszyć jej lepszą wersją.
Jeżeli teraz będziemy chcieli sprawdzać pola, które mają nadane odpowiednie atrybuty (required, pattern, type), możemy wykorzystać metodę checkValidity()
, która zwraca true/false
w zależności od tego, czy dane pole jest wypełnione poprawnie:
<form class="form" method="post">
<div class="form-row">
<label for="name">Imię (min. 3 znaki)*</label>
<input type="text" required name="name" id="name">
</div>
<div class="form-row">
<label for="email">Email*</label>
<input type="email" required name="email" id="email">
</div>
<div class="form-row">
<button type="submit" class="button submit-btn">
Wyślij
</button>
</div>
</form>
const form = document.querySelector("form");
const fieldsRequired = form.querySelectorAll("[required]"); //pobieram pola wymagane
//wyłączamy domyślną walidację by nie wyskakiwały nam przeglądarkowe komunikaty
form.setAttribute("novalidate", true);
//dynamiczne pokazywanie błędów w czasie pisania
for (let field of fieldsRequired) {
field.addEventListener("input", () => {
field.classList.toggle("is-invalid", field.checkValidity());
});
}
form.addEventListener("submit", e => {
e.preventDefault();
let formHasError = false;
for (let field of fieldsRequired) {
if (!field.checkValidity()) {
field.classList.add("is-invalid");
formHasError = true;
} else {
field.classList.remove("is-invalid");
}
}
//jeżeli nie ma błędów wysyłamy formularz
if (!formHasError) {
form.submit();
}
});
Validity State
Każde z pól formularza ma właściwość validity, która wskazuje na obiekt z kilkoma cennymi właściwościami na temat stanu wypełnienia pola:
valid | Zwraca true/false w zależności czy pole przeszło walidację |
---|---|
valueMissing | zwraca true gdy pole wymaga jakiejś wartości, a jej nie podano |
typeMismatch | Zwraca true gdy pole jest typu url/email a podana wartość jest błędna (np. do pola email wpisano wartość, która nie jest emailem, a do url wpisano treść nie będącą adresem) |
tooShort | Zwraca true jeżeli wpisany tekst jest krótszy niż wartość atrybutu minLength |
tooLong | Zwraca true jeżeli wpisany tekst jest dłuższy niż wartość atrybutu maxLength |
patternMismatch | Zwraca true jeżeli pole posiada atrybut pattern, a wpisana wartość nie pasuje do niego |
badInput | Zwraca true jeżeli wpisana wartość nie może być skonwertowana na wymagany format. Na przykład do pola number wpiszemy tekst nie będący liczbą |
stepMismatch | Zwraca true jeżeli pole ma atrybut step , a wartość pola nie pasuje do tego skoku |
rangeOverflow | Zwraca true jeżeli pole posiada atrybut max , a podana wartość go przekroczyła |
rangeUnderflow | Zwraca true jeżeli pole posiada atrybut min , a podana wartość jest od niego mniejsza |
const form = document.querySelector("form");
const input = form.querySelector("input");
form.setAttribute("novalidate", true);
form.addEventListener("submit", e => {
e.preventDefault();
console.log(input.validity);
});