jQuery: semplice validazione di un form

A differenza di qualche tempo fa, in cui ero piuttosto interessato a librerie esterne per validare i miei form, oggi preferisco scrivere e ottimizzare la validazione di volta in volta a seconda del sito in questione.

Troppe librerie, come jQueryTools o jQuery Validator, sono strettamente dipendenti dalla versione di jQuery utilizzata e vanno spesso in conflitto con altri elementi nel DOM.

Come procedere, allora, se siamo alle prime armi?
Un passaggio per volta, vediamo di arrivare ad una validazione completa.

Partiamo con il creare una pagina HTML ed inserirvi il riferimento ad un foglio di stile ed alla libreria jQuery:

<html>
<head>
<meta charset="utf-8">
<title>Form Validation</title>
<link href="stile.css" rel="stylesheet" type="text/css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
...

stile.css

body{
	font-size:12px;
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
}
/* selezioniamo tutti gli input con type=text e le textarea */
input[type=text], #form1 textarea{
	background:#F9F8F6;
	border:1px solid #706f6f;
	width:90%;
	padding:3px;
	font-size:12px;
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-radius:3px;
}
/* diamo uno stile diverso alle select ed ai campi file */
select, #form1 input[type=file]{
	background:#CCCCCC;
	border:1px solid #706f6f;
	width:90%;
	padding:3px;
	font-size:12px;
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-radius:3px;
}
button{
	background-color:#CCCCCC;
	border:1px solid #999999;
	width:90%;
	padding:3px;
	font-size:12px;
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-radius:3px;
}
button:hover{
	cursor:pointer;
	background-color:#b1b1b1;
}
/* stile che assegno ai campi errati */
.redborder{
	border:1px solid #C00 !important;
	box-shadow:0 0 3px #990000;
}
/* stile che assegno ai campi corretti */
.greenborder{
	border:1px solid #0C0 !important;
	box-shadow:0 0 3px #009933;
}

.errormsg{
	display:none;
}
.errormsg h2{
	color:#C00;
	margin:0px;
	padding:0px;
}
.errormsg ul{
	margin-top:0px;
	font-size:11px;
	color:#C00;
	font-weight:bold;

}
.waitmsg{
	display:none;
}

Form:

</pre>
<form id="form1" action="#" method="post" name="form1">
<label for="nome">Inserisci il tuo nome: </label>
<input id="nome" type="text" name="nome" data-description="Nome" />

<label for="cognome">Inserisci il tuo cognome: </label>
<input id="cognome" type="text" name="cognome" data-description="Cognome" />

<label for="email">Inserisci la tua E-Mail: </label>
<input id="email" type="text" name="email" data-description="E-Mail" />

<label for="marca">Seleziona la marca della tua auto: </label>
<select name="marca" data-description="Marca"> <option selected="selected" value="0">Seleziona una voce...</option></select>
<select name="marca" data-description="Marca"> <option value="1">BMW</option></select>
<select name="marca" data-description="Marca"> <option value="2">OPEL</option></select>
<select name="marca" data-description="Marca"> <option value="3">MERCEDES</option></select>
<select name="marca" data-description="Marca"> <option value="4">PORSCHE</option></select>

<input id="privacy" type="radio" name="privacy" value="1" data-description="Privacy" />
<label for="privacy">*<strong>si</strong>, ho letto e accetto il trattamento dei miei dati personali</label>

<button id="submitform1" type="button">Invia la richiesta</button>
<input id="errorState" type="hidden" name="errorState" value="0" />
<div class="errormsg">
<h2>Attenzione</h2>
</div>
<div class="waitmsg">
<h2><img src="img/ajax-loader3.gif" alt="" width="24" height="24" align="absmiddle" /> Attendere prego</h2>
</div>
</form>
<pre>

Alcuni tag sono custom, come ad esempio data-description.
Ho preferito gestire in maniera facoltativa il tag data-description, così da poter descrivere in maniera diversa, dal nome standard del campo, la label del campo stesso.
Nel caso in cui il campo sia
(input id=”email” type=”text”) e volessi scrivere “Il campo E-Mail non è corretto”, allora inserirò il valore “E-Mail” nel tag data-description.
Un sistema veloce, facile da customizzare, non obbligatorio e compatibile anche su IE.

Alla fine del form invece ho un campo con id=errorState e default value=0.
Durante il processo di validazione, mi appoggio al value di id=errorState, modificandolo in value=1 nel caso di errore in qualsiasi campo.
I messaggi di errore, invece, sono listati in un
(ul) dentro il div con class=errormsg.
Non ho inserito alcun input type=submit, ma un semplice button e ne intercetto il click con un semplicissimo bind(), sostituibile ( consigliato ) con un on(), visto che le nuove release di jQuery saranno poco gentili nei confronti di metodi deprecati.

Inserisco prima alcune funzioni:

// per pura velocità di scrittura del codice, mi creo una funzione tolog() ne commento il console.log() quando testo su IE.
function tolog(me){
  console.log(me);
}

// funzione che inserisce nella lista degli errori il singolo errore di validazione
function addToErroMsg(msg){
var msg_final = '</pre>
<ul>
	<li>' + msg + '</li>
</ul>
<pre>
';
  $('.errormsg ul').append(msg_final);
}

Ora intercetto il click del bottone di invio:

$('#submitform1').click(function(){

        // a tutti i campi del form che hanno la class=redborder rimuovo sia la classe redborder che greenborder
	$('#form1 input.redborder, #form1 select.redborder').removeClass('redborder').removeClass('greenborder');
        // svuoto la lista degli errori
	$('.errormsg ul').empty();
        // rimetto il valore di default di errorState
	$('#errorState').val('0');

        // valido i campi sottostanti. il tipo text può essere omesso, gli altri tipi devono essere citati
        // attualmente ho creato una validazione solamente per email, select e radio
	validField('nome');
	validField('cognome');
	validField('email','email');
	validField('marca','select');
	validField('privacy','radio');

        // se errorState==1 allora c'è stato un errore
	if($('#errorState').val()==1){
		$('.errormsg h2').html('Attenzione:');
		$('.errormsg').fadeIn();
	}else{
                // non c'è stato errore di validazione

		$('#submitform1').attr('disabled','disabled');   // disabilito il pulsante di invio
		$('.errormsg').hide();  // nascondo l'eventuale messaggio di errore validazione
		$('.waitmsg').fadeIn(); // mostro il messaggio di attesa
		$('#form1').submit();   // submit del form
	}

})

Ho commentato riga per riga il codice, così vi sarà molto semplice comprendere e personalizzare quando scritto.
Ora non resta che instanziare la funzione validField():

function validField(fieldName,tipo){

	// l'ID è fieldName: ora valuto se ho messo una descrizione diversa
	var fieldDesc = $('#' + fieldName).attr('data-description');
	if(fieldDesc==undefined){
		// il campo ha una descrizione diversa
		fieldDesc = fieldName;
	}

	// validazione per campo TEXT
	if(tipo==undefined || tipo=='text'){
		fieldValue = $('#' + fieldName).val();
		if(fieldValue.length==0){
			// validazione fallita, assegno la classe=redborder
			$('#' + fieldName).addClass('redborder').removeClass('greenborder');
			// errorState=1
			$('#errorState').val('1');
			// messaggio di errore, qui potete personalizzarlo
			addToErroMsg('Campo ' + fieldDesc + ' obbligatorio');
		}else{
			// la validazione è corretta, assegno la classe=greenborder
			$('#' + fieldName).addClass('greenborder').removeClass('redborder');
		}
	} // end type text

	if(tipo=='email'){

		fieldValue = $('#' + fieldName).val();
		if(fieldValue.length==0){
			$('#' + fieldName).addClass('redborder').removeClass('greenborder');
			addToErroMsg('Campo ' + fieldDesc + ' obbligatorio');
			$('#errorState').val('1');
		}else{
			if(fieldValue.indexOf('.')==-1 || fieldValue.indexOf('@')==-1){
				$('#' + fieldName).addClass('redborder').removeClass('greenborder');
				addToErroMsg('E-Mail non valida');
				$('#errorState').val('1');
			}else{
				$('#' + fieldName).addClass('greenborder').removeClass('redborder');
			}
		}
	} // end type email

	if(tipo=='select'){
		fieldValue = $('#' + fieldName + ' option:selected').val();
		if(fieldValue==0){
			$('#' + fieldName).addClass('redborder').removeClass('greenborder');
			addToErroMsg('Campo ' + fieldDesc + ' obbligatorio');
			$('#errorState').val('1');
		}else{
			$('#' + fieldName).addClass('greenborder').removeClass('redborder');
		}
	} // end type select

	if(tipo=='radio'){

		fieldValue = $('#' + fieldName).attr('checked');
		if(fieldValue==undefined){
			$('label[for="privacy"]').css('color','red');
			addToErroMsg('Campo ' + fieldDesc + ' obbligatorio');
			$('#errorState').val('1');
		}else{
			$('label[for="privacy"]').css('color','#999');
		}
	} // end type radio

} //end validField function

Abbiamo lavorato sullo stato di undefined di alcune variabili: su questa azione c’è sempre un aperto dibattito se non sia meglio operare diversamente.
Probabilmente si potrebbe procedere in maniera più “pulita”, ma in questo caso il tempo era poco e il risultato … accettabile.

Se volete vedere la demo di questo form, cliccate qui, mentre da qui scaricate lo zip con il codice.

jQuery: differenze tra .bind() .live() .delegate() e .on()

bind(),  live(),  delegate(),  on(): tutti uguali?

Una delle cose più incasinate in jQuery è capire la differenza che c’è realmente tra i vari metodi .bind(), .live(), .delegate() e .on(), poichè, di primo acchito, sembra che ognuno sia una semplice alternativa dell’altro.

Ovviamente così non è, magari qualche commento a riguardo può chiarire questi passaggi, permettendovi di decidere meglio cosa e come fare.

Per prima cosa dichiaro un po’ di codice HTML che userò per i miei esempi:

<ul id="giocatori" data-role="listview" data-filter="true">
    <!-- ... altri elementi di lista ... -->
    <li>
        <a href="giocatore_dettaglio.html?id=10">
            <h3>Mauro Bergamasco</h3>
            <p><strong>Flanker</strong></p>
            <p>Nazionale italiana di Rugby</p>
        </a>
    </li>
    <!-- ... altri elementi di lista ... -->
</ul>

Il metodo bind()

$( "#giocatori li a" ).bind( "click", function( e ) {} );
$( "#giocatori li a" ).click( function( e ) {} );
$( "#giocatori li a" ).hover( function( e ) {} );

Il metodo bind() collega il tipo di evento e l’handler direttamente nell’elemento DOM in questione. E’ sicuramente il più usato, grazie alla facilità d’uso, ma qualche problema di prestazioni lo causa. Considerate che .click() non è nient’altro che un’abbreviazione di bind() stesso … ecco perchè affermo che bind() è il più usato ;).
Il problema di prestazioni è dato dal fatto che l’handler dichiarato viene applicato a tutti gli elementi del DOM coinvolti dal comando, e quello che succede è vedere reiterato lo stesso handler N volte. Questa cosa non è il massimo a livello di prestazioni.

Il metodo live()

Bastano veramente poche parole per descrivere il metodo live(). Siete soliti usarlo? Allora maggiore sarà il tempo che dovrete impiegare per sostituirlo con un metodo alternativo migliore e, sopra ogni cosa, che non sia deprecato ufficialmente da jQuery dalla 1.7. Ebbene si, notizia ferale.

La problematica principale di live() era quando lo si usava in presenza di pagine molto ricche e soprattutto quando veniva “invocato” in maniera non specifica, utilizzando un selettore generico:

$( "#giocatori li" ).live( "click", function( e ) {} );

Il metodo delegate()

$( "#giocatori" ).delegate( "li a", "click", function( e ) {} );

Live() collega l’handler dell’evento al documento generale, mentre delegate() collega le informazioni, la catena selettore/evento ( “li a”&”click” ) direttamente all’elemento nel DOM ( “#giocatori” ). Questo sistema di collegamento direttamente nel DOM element ha permesso di risolvere molte problematiche di funzionamento, rispetto che infarcire il document di una sfilza di informazioni una dietro l’altra e, cosa ancora più importante, funziona correttamente con gli elementi aggiunti dinamicamente DOPO la fine del caricamento iniziale della pagina.

Il metodo on()

Quello che, stando alle affermazioni di jQuery stessa, dovrebbe essere il vostro obiettivo futuro è proprio on(), che prenderà in un colpo solo il posto di bind(), unbind(), live(), die(), delegate() e undelegate(). Guardare per credere:

// Bind
$( "#giocatori li a" ).on( "click", function( e ) {} );
$( "#giocatori li a" ).bind( "click", function( e ) {} );

// Live
$( document ).on( "click", "#giocatori li a", function( e ) {} );
$( "#giocatori li a" ).live( "click", function( e ) {} );

// Delegate
$( "#giocatori " ).on( "click", "li a", function( e ) {} );
$( "#giocatori " ).delegate( "li a", "click", function( e ) {} );

Come potete vedere, la potenza data a on() è notevole: a seconda di come lo chiamate in causa, lui diventa metamorfico e modifica il modo in cui l’evento è chiamato in causa.
Sicuramente l’instanziare un metodo solo, che prenda il posto di N metodi, aiuta a standardizzare il tutto, rendendolo anche più facile per jQuery da manipolare.
L’unico lato negativo, se così si può chiamare, è che, per poterlo usare, bisogna entrare un attimo nell’ottica di come cambia la gestione dell’evento a seconda di come richiamate il metodo.

Il suggerimento del maestro Zen è quello di studiare il metodo on() e cominciare ad usarlo in tutti i nuovi progetti dove userete la libreria jQuery dalla 1.7 in poi.

Responsive images e jQuery: cambiare immagine a seconda della risoluzione

Seguendo il trend delle ultime settimane, così come ha fatto Adobe nel nuovo Dreamweaver CS6 con il Responsive Design e le sue griglie fluide ( la cui traduzione in italiano suona, a mio avviso, veramente male ), mi sono immerso una mezz’ora in jQuery per risolvere un problema con un sito di un cliente.

La necessità parte dal fatto che l’immagine di sfondo, con proprietà width=100%, non può essere proposta sia sul mobile che su tablet e desktop alla stessa maniera, della stessa qualità. Bisogna essere cortesi nei confronti degli user su mobile, giusto?

Partendo da una immagine, ne realizzo una versione MidRes e una LowRes, e mi accingo a scrivere lo script che mi scali l’immagine in maniera dinamica.

L’immagine di partenza è la seguente:

Prima di tutto, inserisco qualche linea di CSS per eliminare i margini di default del BODY e per mostrare al 100% l’immagine presente in pagina:

Codice CSS:

body{
	padding:0px;
	margin:0px;
}
img{
	width:100%;
	height:auto;
}

Codice HTML:

</pre>
<img id="thisimg" alt="" data-lowres="image_lowres.jpg" data-midres="image_midres.jpg" data-hd="image_hd.jpg" />
<pre>

Come noterete, ho usato alcuni tag custom, dove ho indicato le tre versioni diverse della medesima immagine. Come noterete, inoltre, non ho inserito alcuna immagine in SRC. Noteremo più avanti questa anomalia.

Ora, prima di tutto, scrivo in console un messaggio ogni qual volta viene ridimensionata la finestra:

$(window).resize(function(){
	var altezza = $(window).height();
	var larghezza = $(window).width();
	console.log('it\'s resizing, now it\'s ' + larghezza +'x'+ altezza);
});

Ora, se tutto funziona a dovere, dovreste notare in Console la dimensione della finestra ogni qual volta ridimensionerete il tutto. Non funziona? Controllate di avere inserito il riferimento alla libreria jQuery.

Ora creiamo la funzione che controlla la dimensione e, eventualmente, cambiamo l’SRC dell’immagine.

function CheckRes(object){
	// misuro la dimensione della finestra
	var window_width = $(window).width();

	// controllo l'attuale SRC
	var original_src = $(object).attr('src');

	var img_low = $(object).attr('data-lowres');
	var img_mid = $(object).attr('data-midres');
	var img_hd = $(object).attr('data-hd');

	if(window_width		var new_src = img_low;		// smartphone
	}else{
		if(window_width			var new_src = img_mid;	// iPad
		}else{
			var new_src = img_hd;		// desktop
		}
	}
	if(original_src!=new_src){
		$(object).attr('src',new_src);
	}

}

Cosa fa il codice?
Carica la larghezza della finestra nella variabile original_src, successivamente identifica le 3 versioni differenti dell’immagine e, a seconda della risoluzione, se l’SRC è differente dal nuovo, ne modifica il puntamento. Tutto molto semplice.

Aggiungiamo quindi, in fase di resizing, la funzione creata.

$(window).resize(function(){
	var altezza = $(window).height();
	var larghezza = $(window).width();
	//console.log('it\'s resizing, now it\'s ' + larghezza +'x'+ altezza);
	CheckRes('#thisimg');   // Nota che CheckRes deve ricevere il selettore corretto, #thisimg
});

Ora, al momento del caricamento, la pagina rimarrà vuota e l’immagine verrà caricata correttamente solo dopo un window resizing.
Non ci resta che aggiungere l’ultima riga di codice:

$(document).ready(function(e) {
    CheckRes('#thisimg');
});

Il gioco è fatto.
Fate le varie prove, noterete che ogni volta verrà caricata l’immagine corretta a seconda della dimensione della finestra. Buon responsive design!

Se volete vedere l’esempio in pratica, cliccate qui.

jQuery Transit: trasformazioni e transizioni super fluide con CSS3

Oggi parliamo di jQuery Transit, una libreria per jQuery che facilita la creazione di trasformazioni e transizioni CSS3 attraverso l’uso di jQuery.

La versione commentata e completa pesa solamente 17kb, mentre la minified-version occupa 2kb. Come in tutti i plugin di jQuery che si rispettino, l’utilizzo è di una semplicità disarmante e si basa sull’uso del metodo .transition() invece che sul classico .animate().

Il supporto per i browser è limitato a: IE10, Firefox 4+, Safari 5+, Chrome 10+, Opera 11+ e Mobile Safari. Per quanto riguarda i browser che, ahimè, non supportano nessuna animazione o trasformazione CSS3, non saranno attive le trasformazioni.

Tutti questi plugin permettono attività bellissime dal punto di vista grafico, mentre per quanto riguarda la retro compatibilità avremo sempre molti problemi: è il prezzo da pagare soprattutto alla signora Microsoft ed ai suoi browser, che fino alla versione 9 hanno cavato da noi programmatori / designer davvero il peggio, in termini di parolacce ed insulti.

Sito ufficiale di jQuery Transit: http://ricostacruz.com/jquery.transit/

Perchè realizzare un videotutorial

La nostra idea di realizzare dei videotutorial, spiegando visivamente cosa e come fare le cose, nasce proprio dalla nostra stessa necessità di apprendere alcuni linguaggi, alcuni applicativi: seguire dei videotutorial ben fatti, dove passo passo vengono spiegate le dinamiche causa-effetto, accelera enormemente la curva di apprendimento.

Prendiamo, ad esempio, due linguaggi come jQuery e Sencha.
Il primo ha una curva di apprendimento piuttosto dolce, riuscire a realizzare qualche script carino in jQuery sin dalle prime battute non è cosa difficile.
Discorso ben diverso è quello relativo a Sencha, dove la curva di apprendimento è estremamente ripida ( a detta anche degli stessi moderato del forum di assistenza di Sencha ).

Un video ben fatto, con una buona spiegazione di come e cosa fare e, soprattutto, i passaggi fondamentali da seguire, riteniamo sia un materiale formativo molto prezioso.

Perchè metterli gratis sul sito?
Per dare a tutti la possibilità di imparare, conoscere, senza necessariamente sbattere la testa come dei muli, intestardendosi inutilmente su dei manuali troppo complicati o mal fatti.
Il futuro ci dirà se questa è una scelta che paga o meno.

Chiaramente cerchiamo collaboratori in questa mia avventura. Se siete dei draghi in qualsiasi linguaggio, in qualunque applicazione, fatemelo sapere, saremo ben contenti di condividere gli spazi di Oneblackcat.it con voi.