Formularze - walidacja
Sprawdzanie danych, które wprowadza użytkownik to jedna z ważniejszych rzeczy o jaką musimy zadbać, ale równocześnie też nie zawsze prosta do wykonania. Dane możemy sprawdzać na wiele, wiele sposobów, które często będzie uzależnione od budowy formularza i zawartych w nim mechanizmów.
Kilka informacji na temat struktury HTML formularzaJak przeprowadzić walidację?
- Pierwszy etap to dynamiczna podpowiedź w czasie wprowadzania danych przez użytkownika. Wykorzystamy tutaj zdarzenia change, focus, blur, keyup, keydown, keypress lub input - wszystko zależnie od sytuacji. Dodanie takich dynamicznych podpowiedzi znacząco może poprawić użyteczność naszego formularza, ale nie zawsze jest stosowane. Niektóre z takich testów robiliśmy w poprzednim rozdziale.
- Drugi etap to sprawdzenie danych tuż przed wysłaniem. Jeżeli są poprawne to je prześlemy na serwer. Jeżeli nie, wyświetlamy stosowną informację (ewentualnie wskazujemy błędne pola) i blokujemy wysyłkę.
- Ostatni - najważniejszy etap - to sprawdzenie przesłanych danych po stronie serwera. Jeżeli dane są błędne, wówczas wracamy do formularza wyświetlając informację i czekamy na wykonanie punktu drugiego.
I nie - nie jest to jedyny przepis na sprawdzanie formularzy.
W idealnym świecie do powyższych kroków doszedł by jeszcze krok 0. Od kilku lat w przeglądarkach istnieje bowiem walidacja po stronie HTML. Jeżeli dobrze zbudujemy nasz formularz (dodając do niego odpowiednie atrybuty, stosując odpowiednie typy pól itp) wtedy możemy mieć podstawową walidację bez napisania nawet kawałka Javascript.
Jest to fajna sprawa, ponieważ idealnie wkomponowuje się w progressive enhancement. Jeżeli na stronę użytkownik wejdzie bez Javascript, dostanie podstawową walidację, która może nie jest idealna, ale jest. Na to my jako programiści robimy nakładkę w postaci naszej walidacji Javascript, która - znowu - w idealnym świecie - przeprowadziła by użytkownika przez wszystkie powyższe kroki. Jest to też o tyle fajne, ponieważ w Javascript mamy specjalny interfejs do sprawdzania formularzy (porozmawiamy o nim w kolejnym rozdziale), który swoje działanie opiera na walidacji HTML. Jeżeli więc dobrze stworzymy nasz początkowy kod HTML, nasze zadanie będzie o wiele prostsze.
Problem jest niestety taki, że nie żyjemy w idealnym świecie. Dość często formularze które musimy obsłużyć są tworzone przez jakieś podejrzane pluginy, nie mają odpowiednich pól (dość często spotykaną manierą jest tworzenie pól typu email jako tekstowe), a i nie są stosowane w nich odpowiednie atrybuty. I nie zawsze jest to wina leniwych programistów, ponieważ dość często formularze budowane są na bazie bardziej rozbudowane logiki, która swoje działanie opiera na swoich własnych mechanizmach, a nie tym co zostało wpisane w HTML. Dodatkowo taka domyślna walidacja wcale nie jest pozbawiona pewnych wad.
Tak czy siak walidacji za pomocą Javascript raczej nie unikniemy.
Tak naprawdę ciężko mówić o pełnoprawnej walidacji danych po stronie przeglądarki (pierwszy i drugi krok). Możemy dane sprawdzać i pokazywać użytkownikowi podpowiedzi, ale opieranie się w całości na walidacji danych tylko po stronie przeglądarki jest mocno naiwne. Spreparowanie odpowiedniego formularza, czy nagięcie działania skryptów nie jest zbyt ciężką rzeczą. Szczególnie jeżeli mamy dostęp do narzędzi developerskich (1, 2).
Prawdziwa walidacja danych powinna zawsze się odbywać po stronie serwera. Skrypty w przeglądarce traktuj tylko jako poprawę użyteczności formularza i podpowiedzi dla użytkownika. Nic więcej.
Prosta walidacja
Zacznijmy od podstaw. Domyślną akcją każdego formularza jest wysłanie danych, co powoduje przeładowanie danej strony, lub przeniesienie na inną. Aby wykonać jakąkolwiek walidację, musimy tą akcję przerwać.
Najlepszym sposobem na to jest podpięcie się pod zdarzenie submit
formularza, a następnie użycie preventDefault()
, po którym możemy w spokoju przeprowadzać sprawdzanie pól:
const form = document.querySelector("form");
const input = form.querySelector("input");
form.addEventListener("submit", e => {
e.preventDefault();
//jeżeli wszystko ok to wysyłamy
if (input.value.length >= 3) {
e.target.submit();
} else {
//jeżeli nie to pokazujemy jakieś błędy
alert("Kolego wypełniłeś błędnie nasz super formularz");
}
})
Gdy pojawi się w naszym formularzu kilka pól, trzeba jakoś zebrać wyniki. Rozwiązań jest wiele. Jednym z nich jest ręczne sprawdzanie kolejnych pól, gdzie wyniki testów możemy trzymać w oddzielnej tablicy.
Równocześnie posługiwanie się okienkiem alert nie jest zalecanym rozwiązaniem. Okienko takie jest zbyt "intensywne" dla użytkownika końcowego, a poza tym nie mamy w ogóle możliwości zmiany jego wyglądu. Przyda się w prostych treningowych rozwiązaniach, ale my chcemy więcej. Pokażmy błędy jako informacja wkomponowana w formularz.
<form class="form" method="post" id="form">
<div class="form-row">
<label for="name">Imię (min. 3 znaki)*</label>
<input type="text" name="name" id="name">
</div>
<div class="form-row">
<label for="name">Email*</label>
<input type="email" name="email" id="email">
</div>
<div class="form-message"></div>
<div class="form-row">
<button type="submit" class="button submit-btn">
Wyślij
</button>
</div>
</form>
const form = document.querySelector("form");
const inputName = form.querySelector("input[name=name]");
const inputEmail = form.querySelector("input[name=email]");
const formMessage = form.querySelector(".form-message");
form.addEventListener("submit", e => {
e.preventDefault();
let formErrors = [];
//-------------------------
//2 etap - sprawdzamy poszczególne pola gdy ktoś chce wysłać formularz
//-------------------------
if (inputName.value.length <= 3) {
formErrors.push("Wypełnij poprawnie pole z imieniem");
}
//wyrażenie testujące maila omawiane w rozdziale o wyrażeniach regularnych
const reg = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/;
if (!reg.test(inputEmail.value)) {
formErrors.push("Wypełnij poprawnie pole z emailem");
}
if (!formErrors.length) { //jeżeli nie ma błędów wysyłamy formularz
e.target.submit();
//...lub dynamicznie wysyłamy dane za pomocą Ajax
//równocześnie reagując na odpowiedź z serwera
} else {
//jeżeli jednak są jakieś błędy...
formMessage.innerHTML = `
<h3 class="form-error-title">Przed wysłaniem proszę poprawić błędy:</h3>
<ul class="form-error-list">
${formErrors.map(el => `<li>${el}</li>`).join("")}
</ul>
`;
}
});
W powyższych przypadkach pominęliśmy etap pierwszy, czyli podpowiedzi dla użytkownika w trakcie pisania.
Żeby nie powtarzać warunków testów stwórzmy oddzielne funkcje do testowania i dodawania klasy:
function testText(field, lng) {
return field.value.length >= lng;
}
function testEmail(field) {
const reg = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/;
return reg.test(field.value);
};
function markFieldAsError(field, show) {
if (show) {
field.classList.add("field-error");
} else {
field.classList.remove("field-error");
}
};
A następnie wykorzystajmy je w naszym kodzie podpinając je pod zdarzenia input
lub blur
po wysyłce formularza.
function testText(field, lng) {
...
}
function testEmail(field) {
...
};
function markFieldAsError(field, show) {
...
};
//------------------------
//pobieram elementy
//------------------------
const form = document.querySelector("form");
const inputName = form.querySelector("input[name=name]");
const inputEmail = form.querySelector("input[name=email]");
const formMessage = form.querySelector(".form-message");
//------------------------
//etap 1 : podpinam zdarzenia
//------------------------
inputName.addEventListener("input", e => markFieldAsError(e.target, !testText(e.target, 3)));
inputEmail.addEventListener("input", e => markFieldAsError(e.target, !testEmail(e.target)));
form.addEventListener("submit", e => {
e.preventDefault();
let formErrors = [];
//------------------------
//2 etap - sprawdzamy poszczególne pola gdy ktoś chce wysłać formularz
//------------------------
//chowam błędy
for (const el of [inputName, inputEmail]) {
markFieldAsError(el, false);
}
//i testuję w razie czego zaznaczając pola
if (!testText(inputName, 3)) {
markFieldAsError(inputName, true);
formErrors.push("Wypełnij poprawnie pole z imieniem");
}
if (!testEmail(inputEmail)) {
markFieldAsError(inputEmail, true);
formErrors.push("Wypełnij poprawnie pole z emailem");
}
if (!formErrors.length) { //jeżeli nie ma błędów wysyłamy formularz
form.submit();
//...lub dynamicznie wysyłamy dane za pomocą Ajax
//równocześnie reagując na odpowiedź z serwera
} else {
//jeżeli jednak są jakieś błędy...
formMessage.innerHTML = `
<h3 class="form-error-title">Przed wysłaniem formularza proszę poprawić błędy:</h3>
<ul class="form-error-list">
${formErrors.map(el => `<li>${el}</li>`).join("")}
</ul>
`;
}
});
Błędy przy polach
Możemy też pokusić się o pokazywanie błędów przy błędnych polach. Rozwiązań jest tutaj kilka - lepszych i gorszych - a na pewno różnych.
Najprostsze z nich może polegać na ręcznym wstawieniu komunikatów do HTML za danym polem.
...
<div class="form-row">
<label for="name2A">Imię (min. 3 znaki)*</label>
<input type="text" name="name" id="name">
<div class="form-error-text">Wpisz poprawne imię</div>
</div>
...
Domyślnie takie komunikaty powinny by ukryte (display: none
), a pokazywalibyśmy je gdy pole stawało by się błędne.
Technika dość przyjemna, szczególnie, gdy kod naszego formularza jest generowany przez backend (np. przez Symfony czy podobne backendowe frameworki), a backendowcy w prosty sposób mogą nam wygenerować należyty komunikat błędu w kodzie HTML.
<!-- przykladowy kod w Symfony i Twig -->
{{ form_start(form) }}
<div class="form-row">
{{ form_widget(form.subject) }}
{{ form_errors(form.subject) }} <!-- !!!! ten komunikat -->
</div>
<button type="submit" class="button submit-btn">
Wyślij
</button>
{{ form_end(form) }}
Od strony kodu Javascript nie powinno to być jakieś trudne. Gdy użytkownik będzie wypełniał pole, zaznaczamy je jako błędne. Komunikaty błędów pokażemy dopiero w przypadku próby wysłania błędnego formularza. Jeżeli przy pokazaniu takich komunikatów użytkownik poprawi dane pole, ukryjemy dla tego pola komunikat:
//funkcje testujące
function testText(field, lng) {
return field.value.length >= lng;
};
function testEmail(field) {
const reg = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/;
return reg.test(field.value);
};
function toggleErrorField(field, show) {
const errorText = field.nextElementSibling;
if (errorText !== null) {
if (errorText.classList.contains("form-error-text")) {
errorText.style.display = show ? "block" : "none";
}
}
};
function markFieldAsError(field, show) {
if (show) {
field.classList.add("field-error");
} else {
field.classList.remove("field-error");
toggleErrorField(field, false);
}
};
//pobieram elementy
const form = document.querySelector("form");
const inputName = form.querySelector("input[name=name]");
const inputEmail = form.querySelector("input[name=email]");
const formMessage = form.querySelector(".form-message");
//etap 1 : podpinam zdarzenia
inputName.addEventListener("input", e => markFieldAsError(e.target, !testText(e.target, 3)));
inputEmail.addEventListener("input", e => markFieldAsError(e.target, !testEmail(e.target)));
form.addEventListener("submit", e => {
e.preventDefault();
let formErrors = false;
//2 etap - sprawdzamy poszczególne pola gdy ktoś chce wysłać formularz
//chowam błędy by zaraz w razie czego je pokazać
for (const el of [inputName, inputEmail]) {
markFieldAsError(el, false);
toggleErrorField(el, false);
}
if (!testText(inputName, 5)) {
markFieldAsError(inputName, true);
toggleErrorField(inputName, true);
formErrors = true;
}
if (!testEmail(inputEmail)) {
markFieldAsError(inputEmail, true);
toggleErrorField(inputEmail, true);
formErrors = true;
}
if (!formErrors) {
e.target.submit();
}
});
Dynamicznie tworzone komunikaty
Jeżeli nie mamy wpływu na to jaki będzie HTML, komunikaty o błędach możemy też robić za pomocą samego Javascript. Domyślnie nie będzie ich w HTML, a będziemy je do niego wstawiać dopiero gdy pokażemy dany błąd.
function removeFieldError(field) {
const errorText = field.nextElementSibling;
if (errorText !== null) {
if (errorText.classList.contains("form-error-text")) {
errorText.remove();
}
}
};
function createFieldError(field, text) {
removeFieldError(field); //przed stworzeniem usuwam by zawsze był najnowszy komunikat
const div = document.createElement("div");
div.classList.add("form-error-text");
div.innerText = text;
if (field.nextElementSibling === null) {
field.parentElement.appendChild(div);
} else {
if (!field.nextElementSibling.classList.contains("form-error-text")) {
field.parentElement.insertBefore(div, field.nextElementSibling);
}
}
};
Dodatkowo lekko przerobimy wcześniejszy kod:
//funkcje testujące
function testText(field, lng) {
return field.value.length >= lng;
};
function testEmail(field) {
const reg = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/;
return reg.test(field.value);
};
function removeFieldError(field) {
...
};
function createFieldError(field, text) {
...
};
function markFieldAsError(field, show) {
if (show) {
field.classList.add("field-error");
} else {
field.classList.remove("field-error");
removeFieldError(field);
}
};
//pobieram elementy
const form = document.querySelector("form");
const inputName = form.querySelector("input[name=name]");
const inputEmail = form.querySelector("input[name=email]");
const formMessage = form.querySelector(".form-message");
//etap 1 : podpinam eventy
inputName.addEventListener("input", e => markFieldAsError(e.target, !testText(e.target, 3)));
inputEmail.addEventListener("input", e => markFieldAsError(e.target, !testEmail(e.target)));
form.addEventListener("submit", e => {
e.preventDefault();
let formErrors = false;
//2 etap - sprawdzamy poszczególne pola gdy ktoś chce wysłać formularz
for (const el of [inputName, inputEmail]) {
markFieldAsError(el, false);
removeFieldError(el);
}
if (!testText(inputName, 3)) {
markFieldAsError(inputName, true);
createFieldError(inputName, "Wpisana wartość jest niepoprawna");
formErrors = true;
}
if (!testEmail(inputEmail)) {
markFieldAsError(inputEmail, true);
createFieldError(inputEmail, "Wpisany email jest niepoprawny");
formErrors = true;
}
if (!formErrors) {
e.target.submit();
}
});
Opcjonalnie moglibyśmy jeszcze tutaj nieco zboostować powyższą walidację.
Formularze z większą ilością pól
Powyższe podejście sprawdzi się raczej w stosunkowo krótkich sytuacjach. Nie jest ono złe ale też nie jest żadnym wyznacznikiem jakości. Jeżeli do tej pory sumiennie czytałeś wcześniejsze rozdziały (zwłaszcza ten traktujący o DOM), powinieneś bez problemu stworzyć podobny, a pewnie i lepszy kod.
Problem z powyższym kodem jest taki, że testy kolejnych pól przy większych formularzach będą nam się niemożliwie mnożyć. Musielibyśmy więc w jakiś sposób zautomatyzować nasze podejście.
Jak to zrobić? Najłatwiej było by pobrać wszystkie pola, a następnie robiąc po nich pola wykonać odpowiednie testy. Przydało by się też coś, co da nam jakieś punkty zaczepienia jak dane pole mamy sprawdzać. Niektóre pola mogą wymagać konkretnej liczby znaków, pola z emailem wymagać powinny odpowiedniej wartości, niektóre wymagać będą podania numerów... A dodatkowo nie wszystkie pola muszą być wymagane...
Walidacja w czystym HTML
Nie jest to niestety kursu o HTML/CSS 😥, ale aby dobrze zrozumieć kolejny temat dobrze jest zacząć właśnie od 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 ciekawy koncept, 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 |
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 |
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">Imię*</label>
<input type="text" name="name" id="name" pattern=".{3,}" required>
</div>
<div class="form-row">
<label for="email">Email*</label>
<input type="email" name="email" id="email" required>
</div>
<div class="form-row">
<label for="message">Wiadomość*</label>
<textarea name="message" id="message" required></textarea>
</div>
<div class="form-row">
<button type="submit" class="submit-btn">
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.

Na chwilę obecną jego wygląd możemy zmienić tylko w przeglądarkach Chrome za pomocą poniższego kodu:
::-webkit-validation-bubble {}
::-webkit-validation-bubble-message {}
::-webkit-validation-bubble-arrow {}
::-webkit-validation-bubble-arrow-clipper {}
Ale też możemy za pomocą Javascript zmienić jego treść.
Zauważ, że dla pola #name
zastosowałem dodatkowy atrybut patter .{3,}
, który oznacza minimum 3 znaki dowolnego typu. Atrybut
required
oznacza, że dane pole musi być wypełnione, co dla pól tekstowych oznacza, że trzeba cokolwiek do takiego pola wpisać.
Jeżeli chcemy zmienić ten warunek, musimy używać atrybutu pattern
, w którym podajemy wyrażenie regularne. Nasz wzór idealny nie jest, ponieważ pozwala wpisać dowolny ciąg nawet
(trzy spacje). Żeby realnie sprawdzać imię, trzeba by zastosować bardziej skomplikowany wzór (co wcale proste nie jest), lub celować w grupy znaków unicode (wraz z dodatkami, bo w nazwach mogą być ').
Można tutaj oczywiście walczyć na siłę, ale osobiście znowu zalecam stosować zasadę "im mniej walidacji tym lepiej". Można to przyrównać do captchy w formularzu. Jeżeli zastosujemy zbyt mocną captchę (np. tą od Googla), tylko zniechęcimy użytkowników przed wypełnieniem formularza. Zamiast tego odsiewanie spamu zaleca się robić po stronie serwera...
Wróćmy do formularza.
Dymki z podpowiedzią pokazują się dopiero w naszym 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. Dwa podstawowe selektory to:
- :valid - czy pole jest poprawnie wypełnione
- :invalid - czy pole jest niepoprawnie wypełnione
input:invalid {
border-color: red;
outline: none;
box-shadow: 0 0 2px red;
}
input:required:valid {
border-color: #4dcc23;
outline: none;
box-shadow: 0 0 2px #4dcc23;
}
<input type="text" name="numbers" required pattern="[0-9]{3,}">
Niestety na co dzień te dwa selektory te nie są wystarczające, dlatego dość szybko musimy sięgnąć po dodatkowe:
:required - czy pole jest wymagane
:placeholder-shown - czy dane pole pokazuje w danym momencie placeholder
:focus - czy pole jest aktualnie wybrane
/* plus te same wraz z zaprzeczeniem */
:not(:placeholder-shown)
:not(:valid)
:not(:invalid)
:not(:focus)
Spróbujmy je więc zastosować na powyższym formularzu kontaktowym:
input:invalid,
textarea:invalid {
border-color: red;
outline: none;
box-shadow: 0 0 2px red;
}
input:required:valid,
textarea:required:valid {
border-color: #4dcc23;
outline: none;
box-shadow: 0 0 2px #4dcc23;
}
Udało się? Niezupełnie. błędy w formularzu nie powinny być pokazywane od początku, a dopiero od momentu gdy użytkownik opuści błędnie wypełnione pole (ewentualnie błędy możemy zacząć pokazywać podczas wypełniania pola i po opuszczeniu).
W naszym Javascriptowym rozwiązaniu używaliśmy według potrzeby zdarzeń blur
lub input
, natomiast w czystym CSS na chwilę obecną w zasadzie nie mamy jakiś wielkich możliwości.
Teoretycznie istnieje selektor :placeholder-shown
, który moglibyśmy tutaj wykorzystać:
/* jeżeli pole jest błędne ale i nie pokazuje placeholdera */
input:not(:placeholder-shown):invalid,
textarea:not(:placeholder-shown):invalid {
border-color: red;
outline: none;
box-shadow: 0 0 2px red;
}
input:not(:placeholder-shown):required:valid,
textarea:not(:placeholder-shown):required:valid {
border-color: #4dcc23;
outline: none;
box-shadow: 0 0 2px #4dcc23;
}
Ale jak widzisz rozwiązanie nie jest idealne. Po pierwsze żeby w ogóle je zastosować każde z pól musi mieć swój placeholder, a przecież nie zawsze tak będzie (zobacz jak powyższy formularz niepotrzebnie dubluje informacje). Moglibyśmy tutaj zastosować trik z ustawieniem placeholdera na tekst ze spacją, przy czym wydaje się to znowu hakiem na niedopracowaną rzecz...
Po drugie wsparcie tego selektora nie jest idealne.
Po trzecie jeżeli chcemy wysłać błędnie wypełniony formularz, pojawiają się pokazane powyżej tooltipy z komunikatem, ale dla każdego pola z osobna. Jeżeli mielibyśmy bardziej rozbudowany formularz, musielibyśmy kilkakrotnie próbować wysłać formularz, poprawiać dane i znowu próbować wysłać. To nie jest dobre UX.
Po czwarte - i najważniejsze - nie jesteśmy w stanie poprawić dostępności naszego rozwiązania. Aby nasz formularz był bardziej przyjazny dla osób korzystających z urządzeń asystujących, powinniśmy do naszych pól i błędów dodać dodatkowe atrybuty aria.
W powyższych rozwiązaniach z Javascript umyślnie ominąłem te rzeczy by dodatkowo nie mieszać kodu. Dodanie takiej funkcjonalności do naszego kodu to dosłownie 2-3 linijki.
//za pomocą JS powinniśmy nie tylko dodawać klasę do pola i pokazywać komunikat błędu
//ale i dodatkowe atrybuty aria które wskazują na odpowiedni element
<input type="text" name="name" id="name" aria-invalid="true" aria-describeBy="nameErrorText">
<div class="form-error-text" id="nameErrorText">Wpisz poprawne imię</div>
W CSS nie ma możliwości zarządzać atrybutami...
W Internecie znajdziesz wiele prób stworzenia walidacji w czystym HTML (np. to), ale większość z nich to pójście na kompromis. Czyżby kolejny punkt do listy wstydu CSS?
I znowu - może tobie to wystarczy w danej sytuacji? Widziałem podobne rozwiązania na wielu stronach i spełniało to całkiem swoje zadanie.
Ale powiedzmy, że nie jesteśmy zadowoleni z powyższego rezultatu. Na szczęście możemy tutaj popracować jeszcze za pomocą Javascript. Zrobimy to jednak w następnym rozdziale.
<input type="email" name="email" placeholder="Wpisz email" required>
<div class="form-error-text">Wpisany email jest niepoprawny</div>
A następnie odpowiednio ostylować:
input {}
.form-error-text {
display: none;
}
input:not(:placeholder-shown):invalid {
border-color: tomato;
outline: none;
box-shadow: 0 0 2px tomato;
}
input:not(:placeholder-shown):invalid ~ .form-error-text {
display: block;
}
input:not(:placeholder-shown):required:valid {
border-color: #4dcc23;
outline: none;
box-shadow: 0 0 2px #4dcc23;
}
input:not(:placeholder-shown):required:valid ~ .form-error-text {
display: none;
}