Avrei anche potuto intitolare questo post "principi di form validation", perché quello che vedremo è come sia possibile scriversi un semplice script di validazione form partendo da zero.
È possibile utilizzare il famoso plugin di jQuery chiamato appunto
Validation creato da
Jörn Zaeferrer, che aggiunge in maniera molto semplice delle funzioni personalizzabili per validare un form.
Noi qui non utilizzeremo questa strada, ma guarderemo da vicino quali sono i problemi e le necessità cui si va incontro se si vuole validare un form con jQuery, e ne ricaveremo la flessibilità che si ottiene da una maggiore comprensione.
Il nostro scopo finale è avere un form che viene validato senza il bisogno di caricare un'altra pagina e che invii un'email formattata arbitrariamente, e che al contempo sia sufficientemente flessibile da supportare delle variazioni alla ricetta generale.
L'esempio viene dal form contatti che ho implementato nella "tirata a lucido" del sito
www.bfelettrotecnica.it, e la base da cui partiremo è pari pari quella proposta in
questo tutorial di
Net Tuts+ (a opera di
Eric), e che consiglio di leggere comunque, prima di cominciare.
1. Markup HTML
Come si vede dall'immagine, il form contiene numerosi campi, molti obbligatori (ma non tutti), un select, una textarea e anche due radio per una selezione esclusiva: si consente all'utente di scegliere se rappresenta un privato oppure un'azienda, e di riempire dei campi che variano in base a questa scelta.
Ecco il codice del form:
<div id="contactform">
<form action="#" method="post">
<fieldset>
<legend>Richiesta Informazioni</legend>
<ol>
<li>
<input id="privato" type="radio" value="privato" name="tipologia" /> <span class="radioin">Privato</span><br />
<label for="nome">Nome e cognome <i>*</i></label>
<input id="nome" name="nome" type="text" class="obb" />
<label id='nome_err' class='error'>inserisci il tuo nominativo</label>
</li>
<li>
<input id="societa" type="radio" value="azienda" name="tipologia" /> <span class="radioin">Azienda</span><br />
<label for="azienda">Ragione sociale <i>*</i></label>
<input id="azienda" name="azienda" type="text" class="obb" />
<label id='azienda_err' class='error'>inserisci la ragione sociale</label>
<label for="referente">Referente <i>*</i></label>
<input id="referente" name="referente" type="text" class="obb" />
<label id='referente_err' class='error'>inserisci il nominativo di un referente</label>
</li>
<li style="margin-top: 15px;">
<label for="indirizzo">Indirizzo <i>*</i></label>
<input id="indirizzo" name="indirizzo" type="text" class="obb" />
<label id='indirizzo_err' class='error'>inserisci l'indirizzo dell'attività</label>
<label style="width: 75px;" for="cap">CAP <i>*</i></label>
<input style="width: 50px" id="cap" name="cap" type="text" class="obb" />
<label id='cap_err' class='error'>inserisci il codice di avviamento postale</label>
</li>
<li>
<label for="citta">Città <i>*</i></label>
<input id="citta" name="citta" type="text" class="obb" />
<label id='citta_err' class='error'>inserisci il nome della città</label>
<label style="width: 75px;" for="provincia">Provincia <i>*</i></label>
<input style="width: 50px;" id="provincia" name="provincia" type="text" class="obb" />
<label id='provincia_err' class='error'>inserisci la sigla della provincia</label>
</li>
<li>
<label for="telefono">Telefono</label>
<input id="telefono" name="telefono" type="text" />
</li>
<li>
<label for="email">e-mail <i>*</i></label>
<input id="email" name="email" type="text" class="obb" />
<label id='email_err' class='error'>inserisci il tuo indirizzo e-mail</label>
</li>
<li>
<label for="area">Area d'interesse</label>
<select id="area" name="area">
<option value="void"> </option>
<option value="centraline">Centraline di Controllo</option>
<option value="plc">Programmazione PLC</option>
<option value="Automazione">Automazione Industriale</option>
<option value="Quadri Elettrici">Quadri Elettrici</option>
<option value="Impianti Elettrici">Impianti Elettrici</option>
</select>
</li>
<li>
<label for="messaggio">Messaggio: <i>*</i></label>
<textarea id="messaggio" name="messaggio" rows="5" cols="38" class="obb"></textarea>
<label id='messaggio_err' class='error'>scrivi la tua richiesta</label>
</li>
</ol>
</fieldset>
<span id="input"><input id="submit" type="submit" value="Invia Messaggio" /></span>
<span style="font-style: italic;">I dati personali verranno trattati nel rispetto della normativa sulla privacy.</span>
</form>
</div>
I punti degni di nota sono:
- Nella prima riga, action="#", poiché utilizzeremo jQuery per gestire tutta la parte relativa al passaggio dei dati allo script php che invierà l'email finale.
- I tag <label> dalla classe error sono dei messaggi di errore che verranno visualizzati qualora il rispettivo campo obbligatorio non sia stato compilato. Essi sono nascosti di default.
- I campi obbligatori sono denotati come al solito dall'asterisco (*), inserito in un tag corsivo <i>, e dalla classe obb.
- Il tag <div> che racchiude tutto il form si renderà necessario quando visualizzeremo un messaggio di successo per l'utente.
La classe "error" è dedicata ai messaggi d'errore, mentre la classe "errorinput" è per i campi riempiti erroneamente. Serve qualcosa che salti all'occhio, e in questi casi il colore d'elezione è il rosso. Io ho usato qualcosa di questo tipo, passato attraverso il CSS della pagina:
.error{
color: red;
font-style: italic;
float: left;
}
.errorinput{
border: 1px solid red;
background: #ffcbcb;
color: #000;
margin-bottom: .5em;
}
La proprietà float mi è necessaria nelle righe con due tag input, per non distruggere il layout del form.
2. jQuery: form validation
2.0 validazione di base
La prima cosa da fare è nascondere i messaggi d'errore e prender possesso dell'evento click sul pulsante di invio del form, per poter eseguire le nostre validazioni, invece di inviare subito i dati:
$(document).ready(function(){
$('.error').hide();
$("#submit").click(function() {
// qui ci va tutta la validazione che si attiva quando l'utente intende inviare il messaggio
}
});
E fin qui siamo praticamente pari pari il tutorial di
Net Tuts+.
La validazione che viene ora, prevede di controllare ciascun campo input, e se esso risulta vuoto, visualizzare il corrispondente messaggio d'errore.
Net Tuts+ ha solo tre campi, e quindi scrive la funzione di validazione tre volte, una per ciascun campo: siccome io ho
un sacco di campi, ho velocizzato il tutto con un bel ciclo.
Per prima cosa, si inizializza una variabile,
dataString, che andrà a contenere tutti i dati da passare allo script mail php, e sarà nella forma:
dataString = [nome_campo_1=] + [valore_campo_1] + [&nome_campo_2=] + [valore_campo_2] + ... + [&nome_campo_n=] + [valore_campo_n];
Poi, per analizzare ogni elemento
<input> iterando, si usa il metodo
each(callback): per ogni tag
<input> (che non sia il pulsante d'invio o uno dei due radio all'inizio, e che non sia uno dei campi disabilitati, come vedremo al punto 4, sotto) e per la textarea lo script deve salvare due informazioni: il suo valore (col dato inserito dall'utente), che si ottiene con il metodo
val(), e il suo ID. Con quest'ultimo andiamo subito a creare l'ID del corrispondente messaggio d'errore, aggiungendo il suffisso "_err".
Ecco lo script per queste prime operazioni:
$("#submit").click(function() {
$('.error').hide();
var dataString = "";
$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
var val = $(this).val();
var id = $(this).attr("id");
var err = id + "_err";
});
}
Ho inserito l'istruzione che nasconde i messaggi d'errore anche qui per resettare la validazione ogni volta che si preme il pulsante d'invio.
Ora, la validazione in sé: se il campo è vuoto (
val == "") e il campo è obbligatorio (cioè detiene la classe
obb), è necessario fare due azioni:
- Assegnare la classe d'errore "errorinput" (vista prima) al tag <input> per colorarlo di rosso,
- Visualizzare il corrispondente messaggio d'errore, tramite l'ID manipolato poc'anzi.
Se la validazione passa, si memorizza il dato inserito dall'utente accodandolo alla variabile
dataString.
Inserendo queste operazioni, il ciclo completo diventa come segue:
$("#submit").click(function() {
var dataString = "";
$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
var val = $(this).val();
var id = $(this).attr("id");
var err = id + "_err";
// notifica dell'errore per i campi obbligatori
if ((val == "") && ($(this).is('.obb'))) {
$(this).addClass('errorinput').focus();
$("label[id=" + err + "]").slideDown();
return true;
} else {
$(this).removeClass('errorinput'); // rimozione cautelare della classe errore
// memorizzazione dato valutato
if (i==0) {
dataString = id + '=' + val;
} else {
dataString = dataString + '&' + id + '=' + val;
}
}
});
}
});
Note su quanto inserito:
- Sono necessarie due dichiarazioni per la variabile dataString, perché alla prima iterazione (i==0) non si deve anteporre il simbolo '&' al nome del dato che si memorizza.
- Il messaggio d'errore, nel mio form, è individuale per ciascun campo. Se avessimo usato un unico tipo di messaggio per tutti i campi, il classico "questo campo è obbligatorio" (come fa Net Tuts+), si sarebbe potuto generarlo in questo punto dello script, risparmiando markup nel corpo della pagina.
- Inserire un return true in questo punto significa dire al calcolatore di proseguire con le altre iterazioni del ciclo, senza fermarsi qui (cosa che si sarebbe ottenuta dichiarando return false): a livello pratico, questo vuol dire che tutti gli errori verranno visualizzati contemporaneamente (tutti i campi incompleti diventano rossi e si visualizzano i messaggi corrispondenti). Con return false si evidenzia un errore alla volta, come nel caso del tutorial di Net Tuts+.
Quanto visto finora è sufficiente a validare un form con un qualsiasi numero di campi, riuscendo a distinguere tra valori obbligatori o meno.
2.1 Radio buttons
Adesso si deve implementare la funzione dei pulsanti "radio" che consentono di scegliere tra "privato" e "azienda".
Il concetto è che l'uno esclude l'altro, e quindi se si riempie il campo di "privato" non si riempiono quelli di "azienda", e viceversa. Farlo capire al validatore è importante, perché sono tutti campi obbligatori!
In questo caso lo script deve solo ricordare all'utente di effettuare la scelta, la validazione dei campi risultanti è già stata implementata sopra.
Pertanto aggiungiamo un paio di righe all'inizio della funzione:
$("#submit").click(function() {
var dataString = "";
if ( (! $('#privato').is(':checked')) && (! $('#societa').is(':checked')) ) {
$('.radioin').css('color' , 'red').append(' ←');
$('ol li:first').after('scegli se rappresenti un privato o un\'azienda');
$('#radioerror').hide().slideDown('normal');
return false;
}
//segue ciclo di validazione come visto sopra
Lo script non fa che controllare se nessuno dei radio è cliccato, allorché colora di rosso i titoli (e ci appende pure una freccia per meglio evidenziarli) e visualizza un messaggio.
Il
return false fa sì che lo script si interrompa e nessun'altra validazione venga effettuata se l'utente prima non compie la sua scelta: i campi da validare infatti cambiano, come abbiamo detto.
3: Invio email
3.0 Preliminari
La parte di invio dell'email tramite un file php esterno è esattamente uguale a quella esposta nel tutorial di Net Tuts+, che fa utilizzo del metodo
$.ajax.
Prima però di passare i dati al php, si deve inserire un'istruzione che blocchi lo script se esistono ancora errori nella compilazione del form:
if ($('.error').is(':visible')) {
return false;
}
Poi, in fase di sviluppo, è utilissimo il suggerimento di Net Tuts+ di inserire un alert che stampi il contenuto di
dataString, per controllare che siano stati memorizzati correttamente tutti i dati necessari:
alert (dataString); return false;
Questa riga verrà ovviamente commentata nella versione finale dello script!
3.1 Posting dei dati con jQuery
Adesso possiamo finalmente vedere come passare i dati all'invio email e visualizzare un messaggio di "successo" per l'utente:
$.ajax({
type: "POST",
url: "email.php",
data: dataString,
success: function() {
$('#contact_form').html('');
$('#successo').html('messaggio inoltrato correttamente
')
.append('sarai contattato appena possibile.
').hide().fadeIn('slow');
}
});
return false;
L'opzione
type corrisponde in questo caso all'attributo
method del tag
<form>, e viene settata a POST.
L'indirizzo del file php è relativo alla pagina html dove viene eseguito lo script.
L'opzione
success ci consente di scrivere una semplice funzione che genera un messaggio di "inoltro corretto", e si fa semplicemente svuotando il
<div> che racchiude il form, sostituendo quest'ultimo con un paio di righe: il metodo
html(val) sostituisce tutto il contenuto dell'elemento selezionato con dell'HTML specificato. Nel nostro caso ci mettiamo un blocco che riempiamo con il messaggio scelto. L'ID assegnata al blocco ci consente di stilarlo con del CSS.
Il
return false che chiude il nostro script di validazione serve per impedire al pulsante submit di eseguire la sua azione: tanto abbiamo già fatto tutto noi!
3.2 Script php
Lo script php è davvero semplice:
<?php
if ($_POST['azienda']) {
$body = "Tipologia: Azienda\n\nRagione sociale: ".$_POST['azienda']."\nReferente: ".$_POST['referente'];
} else {
$body = "Tipologia: privato\n\nNominativo: ".$_POST['nome'];
}
$body .= "\n\nIndirizzo: ".$_POST['indirizzo']."\n".$_POST['cap']." ".$_POST['citta']." (".$_POST['provincia'].")\n\nTelefono: ".$_POST['telefono']."\n\nArea d'interesse: ".$_POST['area']."\n\nMessaggio: ".$_POST['messaggio'];
$header = "From: ".$_POST['email'];
mail("indirizzo@email.dominio", "modulo contatti", $body, $header);
?>
La condizione serve soltanto per formattare l'email in modo diverso a seconda che sia stato scelto "privato" oppure "azienda"; poi si aggiungono tutti gli altri campi, che non cambiano.
Quello che è elencato in
body va a finire nel corpo dell'email, mentre lo
header ci consente di specificare l'indirizzo email dell'utente, che il client email userà ad esempio per "rispondere" al messaggio.
Con la funzione
mail si invia l'email all'indirizzo specificato. Il secondo parametro è il subject dell'email, mentre il terzo ed il quarto passano i dati arrangiati prima.
4: Tocchi finali
L'unica cosa indispensabile da fare ancora è disabilitare i campi cui l'utente rinuncia effettuando la scelta tra "privato" e "azienda", perché usiamo questo attributo per escluderli dalla validazione.
E poi, visto che ci siamo, aggiungere qualche funzionalità in più a quei radio buttons non guasta.
Poiché questo deve avvenire prima della validazione, queste istruzioni saranno inserite nello script prima dell'evento click sviluppato nei punti precedenti.
L'evento ideale da intercettare è il cambiamento di stato dei pulsanti, e il metodo
change() è proprio quello che fa al caso nostro; vediamo ad esempio cosa succede quando l'utente clicca sul pulsante per scegliere la categoria "privato":
$('#societa').change(function () {
$('#nome').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
$('label[for=nome] i').empty();
$('label[for=nome]').css('color' , '#999');
$('#azienda, #referente').addClass('obb').removeAttr("disabled");
$('label[for=azienda] i, label[for=referente] i').html('*');
$('label[for=azienda], label[for=referente]').css('color' , '#000');
$('.radioin').css('color' , '#000');
$('.radioin span').remove();
$('fieldset li:eq(0) .error, #radioerror').slideUp();
})
Le righe 2, 3 e 4 agiscono sui campi
cui si rinuncia: essi diventano facoltativi e disabilitati (non ci si può scrivere); per maggiore chiarezza cancello anche l'asterisco e rendo la scritta più "sbiadita" con del grigio, e tolgo eventuali errori di validazione.
Le righe 4, 5 e 6 ripristinano lo stato obbligatorio dei campi prescelti (che potevano essere diventati facoltativi per un'altra scelta privato/azienda).
Le righe 7, 8 e 9 infine resettano eventuali notifiche di errore da parte della validazione.
In questo modo l'utente può cambiare idea quando vuole, e la validazione si adegua.
La stessa funzione, invertendo i riferimenti, si ricopia per l'altro pulsante, e il gioco è fatto.
Un extra interessante da inserire è la disabilitazione del pulsante d'invio mentre lo script sta processando i dati, così, nel tempo tra la pressione del pulsante e la visualizzazione del messaggio di successo, nessun utente particolarmente impaziente potrà inviare il form più di una volta.
Questo è proprio un voler essere perfezionisti, perché con script del genere il tempo di attesa è praticamente nullo! Confesso che questo me lo ha fatto venire in mente un commento al tutorial di Net Tuts+...
Basta disabilitare il pulsante prima della chiamata di
$.ajax():
$('#submit').attr('disabled' , 'disabled');
È bene però riabilitarlo se la pagina viene ricaricata, per consentire all'utente di inviare un nuovo messaggio: questo si fa inserendo l'istruzione come prima cosa nel codice jQuery:
$(document).ready(function(){
$('#submit').removeAttr("disabled");
// e segue tutto il resto...
5: script completo
Ed ecco infine lo script completo, ottenuto mettendo insieme tutti i segmenti esaminati.
$(document).ready(function(){
/* form validation */
$('#submit').removeAttr("disabled");
$('.error').hide();
$('#privato').change(function () {
$('#azienda, #referente').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
$('label[for=azienda] i, label[for=referente] i').empty();
$('label[for=azienda], label[for=referente]').css('color' , '#999');
$('#nome').addClass('obb').removeAttr("disabled");
$('label[for=nome] i').html('*');
$('label[for=nome]').css('color' , '#000');
$('.radioin').css('color' , '#000');
$('.radioin span').remove();
$('fieldset li:eq(1) .error, #radioerror').slideUp();
})
$('#societa').change(function () {
$('#nome').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
$('label[for=nome] i').empty();
$('label[for=nome]').css('color' , '#999');
$('#azienda, #referente').addClass('obb').removeAttr("disabled");
$('label[for=azienda] i, label[for=referente] i').html('*');
$('label[for=azienda], label[for=referente]').css('color' , '#000');
$('.radioin').css('color' , '#000');
$('.radioin span').remove();
$('fieldset li:eq(0) .error, #radioerror').slideUp();
})
$("#submit").click(function() {
$('.error').hide();
var dataString = "";
if ( (! $('#privato').is(':checked')) && (! $('#societa').is(':checked')) ) {
$('.radioin').css('color' , 'red').append(' ←');
$('ol li:first').after('scegli se rappresenti un privato o un\'azienda');
$('#radioerror').hide().slideDown('normal');
return false;
}
$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
var val = $(this).val();
var id = $(this).attr("id");
var err = id + "_err";
if ((val == "") && ($(this).is('.obb'))) {
$(this).addClass('errorinput').focus();
$("label[id=" + err + "]").slideDown();
return true;
} else {
$(this).removeClass('errorinput');
if (i==0) {
dataString = id + '=' + val;
} else {
dataString = dataString + '&' + id + '=' + val;
}
}
});
if ($('.error').is(':visible')) {
return false;
}
//alert (dataString); return false;
$('#submit').attr('disabled' , 'disabled');
$.ajax({
type: "POST",
url: "email.php",
data: dataString,
success: function() {
$('#contactform').html('');
$('#successo').html('messaggio inoltrato correttamente
')
.append('una risposta sarà inviata prima possibile.
')
.append('')
.hide().fadeIn('slow');
}
});
return false;
});
});
Ultima raccomandazione
Utilizzare esclusivamente l'ultima versione di jQuery, perché ad esempio nella versione 1.3.2 c'è un bug con IE8 (e chi se non lui?) che interpreta alla rovescia il check sulla visibilità degli errori, rendendo lo script inutile.
Ulteriori sviluppi
Per andare avanti da qui, si può passare alla validazione avanzata, ad esempio com'è implementata dal plugin Validation, che distingue campi numerici, email, url, ecc. e avverte l'utente quando i dati inseriti non sono conformi al formato atteso.
Il bello di questo è che avviene mentre l'utente sta digitando, e non c'è bisogno di aspettare la pressione del pulsante d'invio.
Questi sono ulteriori passi avanti nell'ottica del
web 2.0, ma una volta inquadrati i concetti di base di cui sopra, non è difficile crearsi un prodotto su misura per le proprie necessità.