Validazione inline in JavaScript con gli eventi giusti
La validazione inline permette di dare un feedback immediato agli utenti mentre compilano i campi di un form. Tra gli eventi JavaScript coinvolti solo due ci consentono di non infastidire l'utente con messaggi inutili. Vediamo quali.
La validazione mentre si digita
Se vogliamo essere certi di infastidire gli utenti, validare i dati mentre un utente li sta digitando nei campi è sicuramente il modo migliore.
L'esempio classico è quello di un indirizzo e-mail. Mostrare un messaggio di errore mentre l'utente deve ancora finire di inserire la sua e-mail è quanto di più deleterio per la user experience.
Dobbiamo infatti essere certi che l'utente abbia finito di interagire con un campo prima di validarlo. Infatti non ha molto senso convalidare dati parziali.
L'evento blur
Come dicevamo prima, dobbiamo essere certi che l'utente non stia più interagendo con un campo in modo da poter validare dati completi.
L'evento blur si attiva quando un campo perde il focus, ossia quando l'utente o è passato al campo successivo o più semplicemente ha effettuato un clic o un tap fuori dal campo.
Possiamo creare un gestore di eventi che riceva come argomento il riferimento DOM al campo di input e:
- Aggiunga una classe CSS di errore al campo in caso di validazione fallita.
- Inserisca un messaggio di errore subito dopo il campo nel DOM.
Ad esempio:
function validateField(input = null) {
if(!input) {
return;
}
input.classList.remove('form-input-error');
if(input.nextElementSibling) {
input.nextElementSibling.remove();
}
const tag = input.tagName.toLowerCase();
const value = input.value;
let isValid = true;
switch(tag) {
case 'input':
const type = input.type;
if(type === 'checkbox' || type === 'radio') {
isValid = input.checked;
} else if(type === 'email') {
isValid = validator.isEmail(value);
} else {
isValid = !validator.isEmpty(value);
}
break;
case 'textarea':
case 'select':
isValid = !validator.isEmpty(value);
break;
default:
break;
}
if(!isValid) {
input.classList.add('form-input-error');
const error = document.createElement('div');
error.className = 'form-error';
error.innerText = input.dataset.error;
input.after(error);
}
return isValid;
}
In questo esempio stiamo usando la libreria validator.js. Basandoci sul tipo di campo, effettuiamo la validazione che andrà a mostrare il messaggio di errore reperendolo dall'attributo di dati data-error impostato su ciascun campo. Ad esempio:
<input required data-error="First Name is required." type="text" id="firstname" name="firstname" class="form-input">
In questo modo i messaggi di errore possono facilmente essere localizzati in più lingue all'atto della generazione della pagina. Notiamo come prima di effettuare la validazione sia la classe CSS di errore che il relativo messaggio vengono rimossi se presenti.
La funzione di validazione restituisce un flag booleano che indica se il valore del campo è valido o meno. Questo si rivela fondamentale per il suo riutilizzo in altri contesti del codice.
Abbiniamo la funzione all'evento blur sui campi in questo modo:
function handleInputValidation(form = null) {
if(!form) {
return;
}
const inputs = form.querySelectorAll('input[required]');
for(const input of inputs) {
input.addEventListener('blur', function() {
validateField(this);
}, false);
}
}
Siamo solo interessati ai campi obbligatori, quindi restringiamo la nostra selezione ai campi del form con attributo required.
L'evento focus
Con questo evento andiamo a resettare l'eventuale errore e la sua classe CSS sul campo corrente che riceve il focus in modo da mantenere la UI pulita.
function resetField(input = null) {
if(!input) {
return;
}
input.classList.remove('form-input-error');
if(input.nextElementSibling) {
input.nextElementSibling.remove();
}
}
Modifichiamo quindi la funzione handleInputValidation() aggiungendo l'evento focus e il suo handler nel loop.
for(const input of inputs) {
input.addEventListener('blur', function() {
validateField(this);
}, false);
input.addEventListener('focus', function() {
resetField(this);
}, false);
}
Il problema dell'ultimo campo e l'invio del form
Allo stato attuale della nostra implementazione, se l'utente inserisce un valore non valido o non inserisce alcun valore nell'ultimo campo del form e prova ad inviare il form, l'evento blur non avrà luogo sull'ultimo campo e quindi la validazione non verrà eseguita.
Ovviamente se l'ultimo campo del form non è di tipo required il problema non sussiste, ma se lo è dobbiamo intercettare l'evento submit del form ed eseguire direttamente la validazione su questo campo.
function handleFormSubmit(form = null) {
if(!form) {
return;
}
form.addEventListener('submit', evt => {
evt.preventDefault();
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.removeAttribute('disabled');
const inputs = form.querySelectorAll('input[required]');
const last = inputs[inputs.length - 1];
const valid = validateField(last);
if(!valid) {
submitBtn.setAttribute('disabled', true);
}
}, false);
}
Selezioniamo l'ultimo campo di tipo required dalla NodeList degli elementi di input e invochiamo su di esso la funzione validateField(). Se il risultato è negativo, disabilitiamo il pulsante di invio del form. Avendo introdotto questa operazione aggiuntiva, dobbiamo modificare la funzione resetField() per riportare il pulsante di invio allo stato originale.
function resetField(input = null) {
if(!input) {
return;
}
input.classList.remove('form-input-error');
if(input.nextElementSibling) {
input.nextElementSibling.remove();
}
const form = input.parentElement.parentElement;
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.removeAttribute('disabled');
}
Demo
Conclusione
La validazione JavaScript ha il compito di migliorare l'esperienza utente fornendogli un supporto che gli permetta di correggere eventuali errori prima di inviare un form. Tuttavia, se tale validazione non viene implementata in modo intelligente e pratico, il risultato finale non potrà che essere deleterio per l'utente finale.