require("../merziablog.php"); Config("title","Spaghetti Ajax: guida ajax in italiano"); include("../header.php"); ?>
Quando ho pensato a questo articolo la mia idea fondamentale era quella di mostrare come passare dall'utilizzo di Ajax nudo e crudo, ad una forma più astratta e potente. Mi sono accorto velocemente che un lettore ha bisogno di conoscere alcuni concetti di programmazione funzionale che i designer di Javascript hanno ben pensato di aggiungere al linguaggio per capire appieno come funzionano alcuni pezzi del codice che mostrerò. Come conseguenza l'articolo ha assunto una nuova forma, quella di una guida veloce ma completa ad Ajax, dalle basi alle astrazioni che erano all'origine lo scopo dell'articolo.
Molto del codice esposto viene utilizzato in un servizio di social bookmarking che sto sviluppando in questi mesi (segnalo.com), dunque le tecniche di programmazione mostrate sono reali, nel senso che sono applicabili a progetti in produzione.
Se leggete in giro su internet forse vi farete l'idea che Ajax è una cosa complicatissima che solo i programmatori più bravi usano. In realtà Ajax è una stupidagine dal punto di vista tecnico, fondamentalmente l'unione di due cose.
Molto spesso si parla di Ajax anche quando soltanto una di queste due caratteristiche viene utilizzata, in ogni caso per capirci qualcosa iniziamo esplorando la prima capacità listata sopra: aggiornare le pagine in temo reale tramite Javascript.
Provate a guardare il codice dell'esempio 1 e ad utilizzarlo (basta premere il bottone Esegui esempio). Alla pressione del bottone il contenuto dell'elemento DIV che ha come id pluto viene modificato da "Come stai" a "Benone!". Guardando il codice si capisce subito che farlo è molto semplice, questa è la funzione script1 che viene richiamata alla pressione del pulsante:
function script1() { var e = document.getElementById("pluto"); e.innerHTML = "Benone!"; }
La funzione script1 chiama la funzione document.getElementById passando come argomento l'identificativo del DIV, e quello che viene ritornato è un riferimento all'oggetto che rappresenta il nostro DIV nella pagina HTML. L'oggetto in questione ha una proprietà chiamata innerHTML che può essere letta/scritta. Se viene letta restituisce il codice HTML presente dentro il DIV, se viene scrittta settandola a qualcosa di diverso come abbiamo fatto noi nell'ultima riga della funzione script1 il contenuto del DIV cambia e gli effetti si vedono immediatamente nella pagina. Facile no?
Altra cosa molto utilizzata in applicazioni Ajax è la possibilità
di poter settare due proprietà dello stile CSS di un oggetto
chiamate visibility e display in modo da fare
scomparire e apparire l'oggetto a piacimento. Bisogna far pratica
anche con questo aspetto (altrettanto semplice) prima di scrivere applicazioni
Ajax serie. Ecco il secondo esempio che illustra come fare a diventare
prestigiatori in Javascript.
Anche in questo caso l'idea di base non è difficile da capire. Prendiamo il riferimento dell'oggetto con document.getElementById come al solito e modifichiamo lo stile dell'oggetto a nostro piacimento tamite oggetto.style.nomeProprietà per fare quello che vogliamo. Qualcuno di voi che non conosce molto bene i CSS si chiederà come mai modifichiamo sia la proprietà visibility che display. La prima viene settata a visible se si vuole che l'oggetto sia visibile, mentre il valore di hidden la fa scomparire. La seconda invece viene settata a none quando si vuole che l'oggetto non prenda più spazio nella pagina, altrimenti anche se invisibile occuperebbe lo spazio ugualmente. A volte ciò è desiderabile, altre volte non lo è. Si noti come la proprietà dispaly venga settata a block quando si vuole far ricomparire l'oggetto: questo valore è infatti il default per un DIV ma varia al variare dei diversi elementi HTML, ad esempio per altri elementi il valore di default è inline. Se volete saperne di più cercate un buon tutorial sui CSS in giro. Congratulazioni! Sapete la metè delle cose che bisogna sapere per utilizzare Ajax in maniera rudimentale. Dunque andiamo a scoprire l'altra metè della storia.
Qual'è la caratteristica principale di una applicazione in Ajax? Il fatto che la pagina si aggiorna senza la necessità di essere ricaricata, o al contrario che un evento generato dall'utente (come ad esempio la pressione di un tasto) causi una operazione sul server, senza la necessita di postare alcun FORM. Abbiamo visto come tramite Javascript sia possibile modificare il contenuto di una porzione della pagina, e di come gli elementi possano scomparire e ricomparire. Questo è sufficiente per creare pagine i cui contenuti si aggiornano in maniera dinamica in linea teorica. In pratica per il sistema avere una utilità reale serve che ad una azione dell'utente vengano visualizzati dati che noi non potevamo avere già in memoria. Immaginate un programma di gestione del magazzino scritto in Ajax. Quando l'utente digita il codice di una particolare merce immediatamente appare nella pagina la quantità ancora disponibile in magazzino. Ovviamente non è pensabile che tutti i dati del magazino siano nella memoria di Javascript, dunque è necessario eseguire una richiesta al server "di nascosto", e appena si ottiene la risposta necessaria aggiornare la pagina.
Altro esempio, una web mail scritta in Ajax (chi non conosce Gmail?). Alla pressione del tasto Delete l'email viene cancellata. Eliminare la riga di quella email dalla lista delle email è semplice, abbiamo già visto come fare. Ma chi dice al server di cancellare l'email dal database se non è possibile spedire un FORM?
Quello che ci vuole per risolvere questi problemi è la capacità di fare richieste al server in maniera asincrona, ovvero in background, senza che l'utente si accorga di niente. In questo modo Javascript può contattare il server spedendo e ricevendo informazioni senza che la pagina venga ricaricata. Ma come si fa nella pratica?
Javascript è capace di fare richieste HTTP in background sia utilizzando il metodo GET che il POST. Noi ci occuperemo solo del primo perchè nella pratica è quello che si utilizza di più e una volta che conoscete questo, se vi fosse necessario, utilizzare l'altro non sarà una cosa complicata. Per poter fare tale richiesta è necessario utilizzare un oggetto che si chiama XMLHttpRequest in tutti i browser moderni escluso Internet Explorer che necessita di un diverso oggetto. Prima facciamo finta che Internet Explorer non esista (ho quasi l'impressione che il mondo riesca ad andare avanti lo stesso). Ciò che serve per fare una richiesta HTTP in Ajax è ovviamente un pò di Javascript, e dall'altra parte (nel server) qualcuno che risponda alla richiesta, ovvero un piccolo script in PHP. Ovviamente l'applicazione server-side può essere scritta in qualunque linguaggio, tutti gli esempi di questo articolo sono scritti in PHP perchè lo conosce anche mia zia Caterina, esperta di programmazione web e di uncinetto, a cui dedico questo articolo.
La prima cosa che vi mostro è la funzione che crea un oggetto XMLHttpRequest.
function CreateXmlHttpReq(handler) { var xmlhttp = null; xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = handler; return xmlhttp; }
Bisogna spiegare cosa fa questo codice. La funzione CreateXmlHttpReq prende in ingresso un solo argomento, una funzione, che verrà chiamata quando la richiesta è stata eseguita. Infatti Javascript è un linguaggio che per l'input/output utilizza un modello basato sugli eventi, ricordate onClick, onChange, eccetera? L'idea è che lui quando non sta fecendo niente per l'utente aspetta che si verifichi un evento associato ad una qualche funzione. Appena l'evento si verifica Javscript entra in azione chiamando l'handler (ovvero una funzione che gestisce l'evento) apposito. Bene, le richieste HTTP necessitano del tempo per essere eseguite, cosa dovrebbe fare Javascript nel mentre, aspettare? Non può perchè deve gestire il resto dell'applicazione. Dunque la richiesta viene lanciata in background. Appena ci sono novità di rilievo l'handler che noi passiamo come argomento a CreateXmlHttpReq viene chiamato per servire la nostra richiesta.
Ora che sappiamo come creare l'oggetto necessario per fare una richiesta HTTP in background, e come associarvi un handler, ci serve sapere come utilizzarlo per richiamare una data pagina e qualche altro dettaglio.
var myRequest = CreateXmlHttpReq(myHandler); myRequest.open("GET","primo.php"); myRequest.send(null);
Come potete immaginare il codice sopra setta l'oggetto XMLHttpRequest in maniera da fargli eseguire una query tramite il metodo GET al file primo.php, e nella riga immediatamente successiva spedisce la richiesta. A questo punto la richiesta parte... ma non abbiamo ancora scritto la funzione myHandler che deve occuparsi di gestirla quando ci sono novità in vista.
function myHandler() { if (myRequest.readyState == 4 && myRequest.status == 200) { alert(myRequest.responseText); } }
Si noti come myRequest sia una variabile globale, infatti l'handler viene chiamato senza arogmenti, non c'è nessuno che gli comunica a quale oggetto si riferisce la richiesta per cui è stato invocato, dunque bisogna avere un riferimento globale all'oggetto XMLHttpRequest. Nel codice finale che viene presentato in questo articolo l'oggetto viene invece registrato nella chiusura (se leggete di piu' capirete cos'e'), cio' permette di gestire in maniera affidabile piu' richieste concorrenti. Ma per ora non ci pensate, facciamo finta che il modo migliore sia avere una variabile globale per l'oggetto XMLHttpRequest.
Il nostro handler viene invocato più volte durante una richiesta HTTP. La proprietà readyState ci dice per quale motivo l'handler è stato chiamato in base al valore che assume.
Ciò che a noi interessa è fare qualcosa quando la richiesta viene completata, per questo testiamo che readyState abbia il valore 4. A quel punto sarà settata anche la proprietà status che contiene il valore dello stato della risposta HTTP, che è 200 per richieste avvenute con successo, il famoso 401 per pagine non trovate, e così via.
Come averete visto il nostro handler è una funzione che si milita a visualizzare un alert. Ora mettiamo le parti assieme e proviamo questo esempio, manca però solo una parte: il file PHP. Ecco dunque il sorgente di primo.php:
<? echo("Questi dati vengono dal PHP"); ?>Ecco l'esempio con il codice completo e la possibilità di vederne gli effetti dal vivo.
Ogni volta che cliccate sul bottone Clicca per lanciare la richiesta, tale richiesta parte, quando la risposta del server è disponibile l'handler viene richiamato con la giusta combinazione di readyState e status e l'alert col contenuto della risposta viene visualizzato. Se avessimo voluto avremmo potuto mettere un DIV nella pagina e tramite la proprietà innerHTML sarebbe stato possibile mostrare il risultato della richiesta aggiornando il DIV direttamente, invece che utilizzare un alert. Questo è un buon esercizio che vi consiglio di fare subito prima di continuare a leggere l'articolo. Tra poco affronteremo argomenti un tantino più complicati dunque è consigliabile giocare un pò con i concetti esposti fino a questo punto per acquisire dimetichezza.
E non lo possiamo ignorare poichè più o meno l'ottanta percento delle persone lo utilizza. Per fortuna non serve modificare che una singola funzione per rendere il nostro codice compatibile con IE, la CreateXmlHttpReq2. Ecco la versione compatibile:
// Create the XML HTTP request object. We try to be // more cross-browser as possible. function CreateXmlHttpReq2(handler) { var xmlhttp = null; try { xmlhttp = new XMLHttpRequest(); } catch(e) { try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } } xmlhttp.onreadystatechange = handler; return xmlhttp; }
Questa funzione tenta di risolvere un certo numero di problemi con diverse versioni di Internet Explorer, e un problema con una vecchia versione del browser Mozilla. Come potete vedere il modo in cui viene creato l'oggetto per la richiesta HTTP e il modo in cui viene settato l'handler variano nei diversi browser, ma gli oggetti sono molto simili e l'handler non deve essere modificato per gestire l'uno o l'altro. Ciò è importantissimo: abbiamo isolato il codice non compatibile tra i diversi browser in un'unica funzione!
C'è un secondo problema con IE in realtà, fa il caching delle richieste HTTP fatte in questo modo, dunque se sono effettuate utilizzando la stessa URL più volte, anche se lo script PHP richiamato produce diversi risultati, il risultato ottenuto sarà sempre lo stesso. Per evitare questo problema invece di richiedere qualcosa come script.php basta richiedere script.php?rand=numeroCasualie. In questo modo inganniamo IE facendogli credere che ogni volta si tratta di una richiesta diversa ed evitiamo il problema della cache. Per generare un numero casuale in Javascript si utilizza la funzione Math.random, dunque per evitare il problema della cache di IE tutto quello che dovremmo modificare nello script di sopra è la riga in cui la richiesta viene settata utilizzando il metodo open. Nello script originale usavamo:
myRequest.open("GET","primo.php");
Mentre sarebbe opportuno utilizzare:
myRequest.open("GET","primo.php&rand="+escape(Math.random()));
A questo punto siamo pronti per riscrivere l'esempio di sopra con alcune modifiche. Per prima cosa utilizzeremo la funzione CreateXmlHttpReq2 compatibile con IE, secondo utilizzeremo il parametro casuale nella richiesta per evitare la cache e terzo modificheremo lo script PHP in modo da ritornare il tempo in secondi, così possiamo vedere che i dati cambiano ad ogni invio. Infine questa volta useremo un DIV per visualizzare il risultato dinamicamente utilizzando innerHTML.
Il codice integrale è il seguente.
Il PHP del file secondo.php è il seguente semplice script:
<? if (isset($_GET['nome'])) { $n = strtolower($_GET['nome']); $name['giorgio'] = "Bianchi"; $name['vittorio'] = "Rossi"; $name['augusto'] = "Verdi"; if (isset($name[$n])) { echo("Il cognome di $n è $name[$n]"); } else { echo("Non conosco il cognome di $n"); } echo("
tempo corrente in secondi: ".time().""); } ?>
Per testare l'esempio sopra provate a immettere i nomi Giorgio, Vittorio, Augusto e premere Visualizza congome, poi provate a mettere un altro nome che non è contenuto nell'array del programma secondo.php, ad esempio Alessio. Avete capito bene come funziona il codice? Complimenti, ora potete dire di sapere come funziona Ajax, e anche spargere la voce sul fatto che le basi sono molto semplici. Ma non illudetevi, Ajax è facile, chiunque può imparare ad usarlo. Quasi tutta la programmazione è così, alla portata di tutti. La differenza che potete fare voi è che se avete talento e voglia potete diventare dei grandi programmatori, capaci di utilizzare astrazioni più potenti che vi rendono il lavoro più facile e veloce. La seconda parte di questo articolo, che è la motivazione per cui la prima parte è stata scritta, si sofferma proprio su questo: l'astrazione, ma prima permettetemi una breve digressione su XML.
XML è una delle tre parti, eppure noi abbiamo utilizzato Ajax fino ad ora senza neppure toccare XML. Come mai? semplicemente chi ha coniato il termine pensava che il modo migliore per comunicare tra il programma che risiede sul server, e il client che fa la richiesta in Ajax, era quello di utilizzare XML. Lo pensano in molti, ma in realtà Ajax non ha niente a che fare con XML. PHP (o chi per lui) e Javascript possono spedirsi i dati in qualunque formato desiderabile. Si noti come di solito la grande quantità dei dati passi dal server al client. Ad esempio in Gmail il server spedisce al client informazioni sui messaggi, e il client si occupa tramite Javascript di visualizzare tali messaggi nella pagina dell'utente. Di solito il passaggio di parametri dal client al server avviene semplicemente passando dei parametri tramite una richeista GET, come abbiamo fatto noi poco fa nel caso del nome.
Per questa ragione uno dei miei metodi preferiti per passare dati dal server all'interprete Javascript del client è utilizzare un formato di semplice testo nel caso di operazioni molto semplici. Ad esempio se devo controllare se uno username in fase di registrazione in un servizio web è già occupato prima di ricaricare la pagina, faccio ritornare dal server un semplice OK o ERR, e controllo dal Javascript se la stringa ritornata è l'una o l'altra.
Se invece è necessario passare molti dati, utilizzo una tecnica diversa. Creo nello script che gira sul server un array Javascript e lo spedisco. Dal lato Javascript uso la funzione eval per valutare l'Array, è già pronta, non mi serve scrivere un parser. Ecco un esempio in PHP.
<? echo('var data = Array("'); echo($a); echo('","'); echo($b); echo(")"); ?>
Semplice no? ma cosa succede se $a o $b contengono caratteri (in questo contesto) speciali come le virgolette? Per evitare qualunque problema basta utilizzare la seguente funzione.
<? function safejsstring($s) { $a = unpack("C*",$s); $res = ""; foreach($a as $byte) { $res .= sprintf("\\x%02x", $byte); } return $res; } ?>
Tale funzione prende una stringa PHP e la converte interamente in sequenze di escape nella forma \xHH dove HH è sono due cifre esadecimali. In questo modo Javascript riuscirà sempre ad interpretare la stringa in maniera corretta anche se fosse costituita da dati binari. Dal PHP potete generare semplicemente anche Array annidati, in breve strutture dati complicate quanto lo desiderate. Se volete usare XML... non vi tiene nessuno ;) ma volevo soltanto che fosse chiaro che non è necessario e il termine è finito dentro questa tecnica di programmazione solo perchè l'autore dell'acronimo ha deciso così.
Carino Ajax vero? Ma se pensate a quanto codice abbiamo scritto per fare una mezza richiesta HTTP in Javascript, che abbiamo utilizzato in maniera esplicita una variabile globale, e quanto poco astratto dal reale problema che risolve risulta il nostro codice, non c'è mica da essere orgogliosi... dobbiamo proprio correre ai ripari.
Benvenuti nella seconda parte di questo articolo, dove (se non lo sapete già), imparerete come innalzare il livello del vostro Javascript utilizzando i concetti della Programmazione Funzionale. Se non sapete di cosa si tratta vi consiglio di leggere questa parte anche se non siete interessati a sapere di più su Ajax. In ogni caso i concetti che seguono sono assolutamente necessari per seguire la parte più avanzata dell'articolo, in cui saranno usati per creare una piccola libreria Ajax che ci libera dal tedio di fare tutto a mano, e che sembra integrata nel linguaggio.
Utilizzando linguaggi che sono fondamentalmente imperativi, come il C, Java, PHP, Javascript (anche se l'ultimo come vedremo ha un buon supporto per la programmazione funzionale), una delle cose che capita spesso di fare è quella di scorrere un array indice dopo indice tramite un ciclo for.
Partiamo da un esempio reale... una operazione spesso necessaria è, dato un array, prendere l'elemento col valore massimo. Serve per un sacco di cose, una tra tutte: avete presente la nuova moda web 2.0, di visualizzare dei tag con un font o con un colore proporzionale alla sua popolarità? (più spesso proporzionalmente al logaritmo della popolarità). Bene, la prima cosa che serve per fare la proporzione è ovviamente sapere qual'è il valore del massimo elemento.
In javascript scrivereste abbastanza ovviamente:
var a = Array(10,5,7,81,28); var max = a[0]; for (i = 1; i < a.length; i++) { if (a[i] > max) { max = a[i]; } }
E se vi serve sapere qual'è la stringa più lunga di un array? Vi serve un altro ciclo for. Una volta un programmatore ha detto Se per ogni ciclo for che ho scritto nella mia vita potessi ricevere un centesimo sarei ricco. Come possiamo liberarci da questa schiavitù del ciclo for? La soluzione è data dal fatto che in Javascript le funzioni sono oggetti di prima classe, che in breve significa che le funzioni così come un oggetto stringa o un numero possono essere passate come argometi ad altre funzioni e ritornate come valore di ritorno da funzioni. In pratica non c'è differenza sostanziale tra quello che si può fare con un numero, con una stringa e con una funzione. Ora abbiamo la soluzione al nostro primo problema, possiamo scrivere una potente funzione che si chiama reduce.
function reduce(a,f) { if (a.length == 0) { throw emptyArrayError; } var res = a[0]; for (var j = 1; j < a.length; j++) { res = f(res,a[j]); } return res; }
Questa piccola funzione prende in ingresso due oggetti: il primo è un array, il secondo è una funzione f. Applica f ai primi due elementi dell'array, e prende il risultato. Poi applica f al risultato e al terzo elemento dell'array, e così via. in pratica se L'array contiene i valori 1,2,3,4 il risultato di reduce è f(f(f(1,2),3),4).
Come vedete la funzione f che viene passata a reduce prende in input due argomenti, li compara, e ritorna uno dei due. Se f fosse una funzione che dati due argomenti ritorna il più grande dei due, il risultato di reduce(a,f) sarebbe il massimo elemento dell'array! Bene, ora siamo in grado di scrivere del codice che prende l'elemento più grande di un array senza ricorrere ad un clico for usando reduce. Ecco come.
function max(a,b) { if (a > b) return a; else return b; } var a = Array(10,5,7,81,28); var max = reduce(a,max);
In pratica abbiamo estrapolato quello che c'è di uguale in molti cicli for e lo abbiamo separato dal resto del codice, incarnandolo nella funzione reduce. Rimane un problema, abbiamo dovuto scrivere la funzione max, e ciò può essere noioso. Ora che abbiamo reduce vorremmo utilizzarla in tanti piccoli contesti quotidiani, e scrivere continuamente funzioni aiutanti è una noia. Ma Javascript non è un linguaggio smidollato! Infatti ha un concetto molto utile, le funzioni anonime. Se in Javascript le funzioni sono oggetti come tutti gli altri, perchè devono avere per forza un nome? Una stringa non ha un nome, neppure un numero. Anche le funzioni possono non averlo, possono essere scritte in maniera "letterale", o messe dentro una variabile, come tutti gli altri tipi di dato. Basta usare la seguente notazione.
var a = Array(10,5,7,81,28); var maxfunction = function(a,b) { return (a>b) ? a : b }; var max = reduce(a,maxfunction);
Così come Array(1,2,3) ritorna un array, function(parametri) {codice} ritorna una funzione. Dunque nell'esempio sopra abbiamo creato una funzione e abbiamo messo il suo riferimento in una variabile, e poi l'abbiamo usata come argomento di reduce. Ma perchè usare una variabile intermedia? Ecco dunque la versione finale, in tutto il suo splendore ;)
var a = Array(10,5,7,81,28); var max = reduce(a,function(a,b){return (a>b)?a:b;});
Confrontate questo codice per trovare il massimo elemento di un array con quello che avevamo scritto utilizzando il ciclo for all'inizio. Non solo questo, reduce fa tutto quello che volete... avete voglia di sommare l'array? Bene:
var a = Array(10,5,7,81,28); var sum = reduce(a,function(a,b){return a+b;});
Il limite è la vostra fantasia.
Anche se per i nostri scopi la cosa importante è la comprensione delle funzioni anonime (che permetteranno di capire cosa sono le chiusure nel prossimo paragrafo), la programmazione funzionale è uno strumento potentissimo, dunque illustro brevemente altre due classiche primitive della programmazione funzionale, map e filter.
Come reduce anche map prende come input un array e una funzione f, ma invece che ridurre l'array ad un solo elemento applicando una funzione come fa reduce, map ritorna un nuovo array in cui ogni elemento è stato ottenuto trasformando il vecchio elemento nel nuovo tramite una funzione f.
In termini pratici, dato l'array 1,2,4,7 ciò che ritorna map(a,f) è f(1),f(2),f(4),f(7). Ecco la funzione map.
function map(a,f) { var res = Array(); for (var j = 0; j < a.length; j++) { res[j] = f(a[j]); } return res; }
Questa volta la funzione f che passiamo come argomento a map è una funzione che prende un solo argomento (una funzione unaria), e ritorna un valore. Ecco un esempio di applicazione di map, che non fa altro che trasformare un array di numeri in un array dei quadrati di tali numeri.
var a = Array(1,5,4,3); var b = map(a,function(x){return x*x});
Alla fine del programma l'array b conterrà gli elementi 1,25,16,9. Come potete immaginare è possibile sommare gli effetti di map e reduce, ad esempio provate a scrivere il codice che utilizzando queste due primitive sommi i quadrati degli elementi di un array. Si noti come anche se teoricamente siamo interessati soltanto al valore di ritorno della funzione f, sia possible forzare map a lavorare come una sorta di foreach.
var a = Array(1,5,4,3); map(a,alert);
Queste due righe di codice avranno l'effetto di fare l'alert di tutti gli elementi dell'array. Il risultato della funzione map non viene utilizzato in nessun modo perch&egrabe; non è nostro scopo, in questo caso siamo solo interessati nell'effetto collaterale (o side effect) che ha la chiamata della funzione f (ovvero alert), che è quello di visualizzare i numeri.
L'ultima delle funzioni che giocano un ruolo importante tra le primitive della programmazione funzionale su cui ci soffermeremo è filter. Questa funzione sarà solo descritta in quanto concettualmente molto simile alle altre due. L'implementazione di filter è lasciata come esercizio al lettore. Ma cosa fa filter? Dato un Array e una funzione unaria f in ingresso, ritorna un Array composto solo dagli elementi per cui la chiamata alla funzione f restituisce true. In pratica filter filtra l'array e ne crea uno che ha come elementi solo quelli che piacciono alla funzione passata come argomento, che utilizza il valore di ritorno true o false per indicare se tenere o scartare ogni dato elemento
Utilizzando assieme map, filter e reduce è per esempio possibile in una singola linea di codice processare un array per tenere solo gli elementi che sono maggiori di 10, elevarli al quadrato e ottenerne la somma. Ma ora è tempo di chiudere questa (spero interessante) parentesi e continuare il nostro breve incontro delle caratteristiche evolute di Javascript.
La più semplice definizione di chiusura, e anche una delle meno immediatamente comprensibili per chi non le conosce, è la seguente: una chiusura è una funzione con uno stato ad essa associato. Cosa significa di preciso? Per spiegarlo bisogna fare un passo indietro, e ritornare alle funzioni anonime di cui abbiamo parlato prima (se non ricordate bene cosa sono, tornate indietro a fare un ripasso e poi ritornate qui!). Abbiamo detto che in Javascript le funzioni sono un oggetto di prima classe, e abbiamo infatti visto come sia possibile passare una funzione come argomento ad un'altra funzione. Abbiamo anche affermato che è possibile scrivere funzioni che ritornano funzioni come argomento. Ad esempio il codice
function creaFunzione() { var f = function() {return 1}; return f; } var b = creaFunzione(); alert(b());
quando viene eseguito mostra un alert in cui c'è scritto 1. La funzione creaFunzione ritorna una funzione che non fa altro che ritornare 1 (scusate il gioco di parole, ma è proprio cosi, creaFunzione ritorna una funzione che quando viene chiamata ha come valore di ritorno l'intero 1). Tale funzione ritornata da creaFunzione viene salvata dentro la variabile b. Da questo momento in poi b può essere usata come una funzione, infatti la chiamiamo nell'ultima riga del programma di esempio, per ottenere l'argomento di alert.
Fin qui nulla di nuovo. Esistono le funzioni anonime, e come tutte le altre funzioni sono oggetti di prima classe: possono essere utilizzato come argomento, ritornate da altre funzioni, tenute dentro le variabili, e qualunque altra cosa fareste con un numero, una stringa o un array (anch'essi tutti oggetti di prima classe). Ma cosa accade se nel corpo di una funzione anonima c'è un riferimento ad una variabile che è definita al momento della sua creazione nello scopo esterno ad essa? Un esempio chiarirà cosa voglio dire.
function creaFunzione(valore) { var f = function() {return valore}; return f; }
La variabile valore esiste mentre la funzione creaFunzione viene eseguita, e come potete vedere compare nel corpo della funzione anonima che alla fine restituiamo come valore di ritorno. Ma quando utilizzeremo la funzione anonima creata, creaFunzione non sarà più attiva, dunque cosa contiene la variabile valore quando la funzione anonima viene invocata?. Concentratevi che questo è un passaggio delicato. Ciò che accade è che la funzione anomina create ricorderà il riferimento all'oggetto che aveva la variabile valore al momento in cui la funzione anonima è stata creata. Dunque se scrivo
a = creaFunzione(10); b = creaFunzione(20); a(); // ritorna 10 b(); // ritorna 20
Le funzioni a e b ritorneranno ad ogni chiamata rispettivamente 10 e 20! Ecco cosa significa che una chiusura è una funzione con uno stato ad essa associato. La chiusura ricorda tutti i riferimenti a variabili esterne al momento della sua creazione. La cosa interessante è che questo stato che la chiusura si porta appresso può anche essere modificato.
Per esempio è possibile scrivere una funzione che ritorna delle funzioni che contano.
function creaContatore() { var c=0; return function() { return c++; } } var a = creaContatore(); var b = creaContatore(); a(); /* ritorna 0 */ a(); /* ritorna 1 */ a(); /* ritorna 2 */ b(); /* ritorna 0 */ b(); /* ritorna 1 */ a(); /* ritorna 3 */
Ad ogni chiamata a() e b() ritornano l'intero successivo (partendo da zero), e ricordano dove erano arrivate. Siccome questo non è un corso completo di programmazione funzionale non mi posso spingere oltre, ma provate a immaginare le possibilità... (argh, sembra lo spot della Apple).
C'è un campo in cui è necessario soffermarsi sull'applicazione delle chiusure: le funzioni che gestiscono eventi in Javascript, ovvero i così detti handler. Partiamo da un problema reale, e vediamo come viene affrontato tramite le chiusure. In una pagina HTML ci sono due elementi: uno spazio in cui l'utente può scrivere qualcosa (un banale elemento INPUT di tipo text) e un bottone. Alla pressione del bottone Javascript fa partire un timer, che dopo dieci secondi mostrerà un alert che visualizza cosa c'era scritto nell'INPUT al momento in cui l'utente aveva premuto il tasto. Eccovi l'esempio funzionante.
L'utete potrebbe ad esempio scrivere hello e premere il tasto, poi scrivere world e premere il tasto. Dopo alcuni secondi vedrebbe in successione due alert, uno in cui è scritto hello e l'altro in cui è scritto world. Per implementare un tale programma qualcuno potrebbe essere tentato di scrivere qualcosa come:
function whenButtonIsPressed() { setTimeout(showAlert, 10000); } function showAlert() { var e = object.getElementById("inputobj"); alert(e.value); }
Nell'esempio immaginiamo che il bottone abbia un evento onClick che richiama la funzione whenButtonIsPressed e che il testo digitato dall'utente sia contenuto in un elemento INPUT con ID inputobj.
Ovviamente il codice mostrato sopra non funziona, perchè l'handler del timer, ovvero la funzione showAlert, va a leggere il contenuto dell'INPUT solo quando deve visualizzare l'alert. Se nel mentre il contenuto è cambiato perchè l'utente nel corso dei 10 secondi ha scritto qualcosa di nuovo il nostro gioco non funziona. Noi vogliamo che alla pressione del tasto, l'alert risultante 10 secondi dopo visualizzi quello che c'era scritto quando il tasto era stato premuto. Ci servirebbe un handler che ha memoria del passato... ovvero, una chiusura! Ecco il codice corretto:
function whenButtonIsPressed() { var string = document.getElementById("inputobj").value; var myHandler = function() { alert(string); }; setTimeout(myHandler, 10000); }
Abbiamo risolto il problema con un'unica funzione. La chiusura che usiamo come handler ha un riferimento a string, dunque si crea un collegamento tra l'oggetto che conteneva tale variabile al momento della creazione della funzione e la funzione stessa.
Nota: questo particolare problema potrebbe essere risolto anche tramite eval(), ma ci sono molti casi in cui le chiusure risolvono problemi non risolvibili con eval() e tantissimi altri casi in cui risolvono i problemi in maniera molto più elegante.
Siamo prossimi alla conclusione. Con le conoscenze acquisite ci proponiamo di scrivere delle funzioni che rendono Ajax così semplice che per fare una query in Ajax e mostrare la risposta in un alert basterà scrivere:
ajaxGet("primo.php",myHanlder); function myHandler(content) { alert(content); }
In pratica la funzione ajaxGet dovrà predisporre la query, e poi chiamare l'handler passando come primo argomento il risultato della richiesta HTTP solo se la richiesta è andata a buon fine. Non c'è nessuna variabile globale, nessun controllo dello stato, tanto meno la ripetizione del codice necessario per creare l'oggetto necessario alla richiesta e alla sua inizializzazione. Ma non ci accontentiamo di questo, l'altra importantissima caratteristica che deve avere la nostra nuova interfaccia verso Ajax è la seguente: qualunque argomento addizionale passato alla funzione ajaxGet dovrà essere passato all'handler quando la richiesta viene completata, dunque il codice:
ajaxGet("primo.php",myHandler,"a",10); function myHandler(content,first,second) { alert(content); alert(first); alert(second); }
Avrà l'effetto di eseguire la richiesta e chiamare l'Handler con la risposta ottenuta dal server come primo argomento, la stringa "a" come secondo argomento e l'intero 10 come terzo. Riflettete un pò su questa caratteristica e vi accorgerete come ora la richiesta sia in grado di portarsi appresso lo stato, e permetta dunque di usare lo stesso handler per gestire diverse situazioni. Infatti dietro l'implementazione di ajaxGet ci sono proprio le chiusure, abbiamo preso i vantaggi della closure e li abbiamo trasportati nella nostra API, anche se nella pratica per usare tale API non serve conoscere le chiusure.
E' importante notare come la possibilità di passare argomenti addizionali all'handler aumenti la potenza della nostra interfaccia. Immaginate di avere due divesi elementi DIV nella pagina. Volete fare delle richieste a diversi file PHP e visualizzare il contenuto in un DIV o nell'altro tramite innerHTML. Potete semplicemente scrivere:
ajaxGet("primo.php",myHandler,"primodiv"); function myHandler(content,elementid) { var e = document.getElementById(elementid); e.innerHTML = content; }
Se più tardi volete fare una nuova richiesta ma visualizzare il risultato in un DIV diverso, potrete riutilizzare il vecchio handler e scrivere semplicemente:
ajaxGet("primo.php",myHandler,"secondodiv");
Vedrete come nella vita reale qualunque applicazione Ajax non banale presenta questo tipo di necessità. Ora che abbiamo visto come vorremmo che fosse la nostra interfaccia verso Ajax, andiamo ad implementarla utilizzando le tecniche di programmazione imparate fino ad ora e qualcuna nuova che sarà esposta quando occorre.
var ajax_req = null; function ajaxOk() { if (ajax_req.readyState == 4 && ajax_req.status == 200) { return ajax_req.responseText; } else { return false; } } function ajaxGet(url,handler) { var a = new Array("placeholder"); for (var j=2; j<arguments.length; j++) { a[a.length] = arguments[j]; } var myhandler = function() { var content = ajaxOk(); if (content != false) { a[0] = content; return handler.apply(this, a); } } ajax_req = CreateXmlHttpReq(myhandler); ajax_req.open("GET",url); ajax_req.send(null); }
La funzione ajaxOk svolge un compito banale, se avete ancora fresca nella memoria la prima parte dell'articolo ricorderete che le proprietà readyState e status dell'oggetto xmlHttpReq vengono utilizzate per controllare lo stato della richiesta, ovvero se è andata a buon fine. In caso positivo, tramite la proprietà responseText si può ottenere il contenuto della riposta HTTP. Alla luce di questi fatti si vede subito che ajaxOk non fa altro che controllare se la richiesta registrata nella variabile globale ajax_req sia terminata con esito positivo, e in tal caso ritornare la risposta ottenuta. In caso contrario invece viene ritornato false.
Analizziamo ora il pezzo forte, ovvero ajaxGet. Questa è una funzione che utilizza un numero variabile di argomenti. In Javascript ogni funzione ha accesso esplicito alla lista di argomenti con cui è stata chiamata tramite l'array arguments. Quello che fanno le prime righe di codice di ajaxGet è predisporre un array con il primo elemento settato a una stringa arbitraria (che sarà poi rimpiazzata dal testo della riposta HTTP), e tutti gli altri argomenti settati al valore passato dall'utente ad ajaxGet come argomenti addizionali. In pratica questo array sarà utilizzato per chiamare l'handler quando la richiesta Ajax è completa. Infatti sotto si procede alla creazione dell'handler vero della richiesta, quello che poi chiamerà l'handler più astratto, in pratica il vero handler fa da wrapper o da interfaccia per usare un termine italiano a quello di più alto livello. Utilizzando la funzione ajaxOk il vero handler controlla che la richiesta abbia avuto buon fine (altrimenti non fa nulla), in caso positivo setta il primo elemento dell'array a alla risposta ottenuta. Ora l'array a contiene una la lista degli argomenti con cui richiamare l'handler di alto livello, e il gioco è fatto. Si noti come sia l'array a che l'argomento handler della funzione ajaxGet siano memorizzati nella chiusura assegnata alla variabile myHandler. Ecco dunque a cosa servivano le chiusure in questa nostra libreria.
Siamo all'ultima riga di codice che rimane oscura:
return handler.apply(this, a);
Cosa fa apply?. Il problema è il seguente: l'handler reale deve chiamare il nostro handler di alto livello con la lista di argomenti settati nell'array a. Come si richiama una funzione utilizzando come argomenti gli elementi di un array? In breve se ho un array che contiene gli elementi "a", "b" e "c", e una funzione f(), vorrei avere qualcosa che produca l'effetto di una chiamta a f("a","b","c"). Il metodo apply di ogni oggetto funzione Javascript fa proprio questo. A questo punto dovrebbe essere tutto chiaro, le seguenti righe di codice non fanno altro che creare la richiesta e assegnarla alla variabile globale ajax_req, e finalmente spedire la richiesta.
Come avete visto anche se nell'utlizzo della nuova interfaccia non c'è traccia di variabili globali, nella implementazione l'abbiamo utilizzata. Ciò era in questo caso desiderabile perchè la funzione CreateXmlHttpReq si occupa sia di creare l'oggetto necessario alla richiesta XmlHttpReq che di settare l'handler. Separando tale funzione in due parti, una che crea la richiesta ed una che setta l'handler, la necessità di una variabile globale non sussiste più, ma poichè viene fatta al massimo una richiesta Ajax per volta e non volevo rimettere le mani nel codice esposto nella prima parte per non creare confusione ho preferito questa soluzione. In realtà non c'è alcun motivo per non registare anche l'oggetto della richiesta nella chiusura, dunque una buona esercitazione è quella di riscrivere il codice in modo da evitare le variabili globali (anche se nella vita reale l'implementazione esposta potrebbe essere più conveniente perchè la parte incompatibile tra i diversi browser viene isolata in una sola funzione).
Purtroppo il codice sopra esposto non funzionerà con Internet Explorer 5, perchè tale browser non implementa apply. Siccome ancora c'è circa l'un percento della popolazione di internet che utilizza tale browser è bene sopportarlo in qualche modo. A tal fine ho scritto una implementazione di apply utilizzando la funzione eval:
function myDummyApply(funcname,args) { var e = "funcname("; for (var i = 0; i < args.length; i++) { e += "args["+i+"]"; if (i+1 != args.length) { e += ","; } } e += ");" return eval(e); }
Anche ajaxGet va modificata per utilizzare tale funzione se la apply reale fallisse. Ecco dunque il codice integrale completo di ogni pezzo che implementa l'interfaccia Ajax comoda da usare.
(30 Maggio 2007) Nota: la funzione e' stata aggiornata per memorizzare l'oggetto della richiesta Ajax nella chiusura, questo significa che la nuova funzione ora gestisce richieste simultanee senza problemi. Questa modifica e' stata fatta nel codice che uso in produzione da un anno ma ho sempre dimenticato di metterla online, scusate per l'inconveniente. Consiglio a tutti di utilizzare questa nuova versione.