In questo secondo articolo analizziamo una prima libreria JavaScript che ci permette di risparmiare codice (e sudore) nella realizzazione di un semplice esempio di autocompletamento. Vedremo innanzitutto lo sforzo che un tale esempio richiede scrivendo a mano ogni singola riga di codice JavaScript, e poi realizzeremo lo stesso esempio con l‘aiuto delle librerie Prototype e Script.aculo.us
Uno scenario applicativo: l’autocompletamento
Nel primo articolo abbiamo visto come utilizzare JavaScript per realizzare e organizzare il ciclo di vita di un’interazione Ajax. Abbiamo visto la struttura di base di questa interazione.
Gli scenari di impiego di Ajax possono essere diversi, dalla validazione asincrona lato server dei dati di una input form all’autocompletamento, da un refresh automatico dei dati a una simulazione di notifiche lato server, e altri ancora. Si può intuire, una volta vista la sequenza di istruzioni che stanno alla base di Ajax, come questa “sequenza” possa essere resa trasparente al programmatore e racchiusa in un’unica chiamata JavaScript.
Ora vedremo in dettaglio un esempio classico di come Ajax viene utilizzato in un numero sempre più grande di applicazioni Web: l’autocompletamento.
Andremo prima ad analizzare la quantità di codice che un tale scenario applicativo richiede volendo scrivere la sequenza di passi descritti nel primo articolo della serie uno a uno; poi passeremoo a vedere lo stesso esempio, facendoci però “aiutare”, per così dire, dall’accoppiata di librerie Prototype [1] e Script.aculo.us [2] in modo da notare il sostanzioso “risparmio” di codice che l’uso di tali librerie comporta.
Una chiamata Ajax si può dividere:
lato client:
- a. l’inserimento dei dati nell’interfaccia grafica che identificano la richiesta;
- b. una serie di istruzioni JavaScript che si occupano di inoltrare questa richiesta al server
lato server
- c. la preparazione della risposta e l’invio della stessa al client
Andiamo ad illustrare i singoli passi.
a. Il corpo di una pagina JSP che contiene un campo di input per la richiesta:
Ajax Auto Complete
Provincia:
b. Le funzioni JavaScript che costituiscono il cuore di Ajax:
function trovaProvince(){
...
if(inputField.value.length > 0){
createXMLHttpRequest();
var url = "AutoCompleteServlet?names=" + escape(inputField.value);
xmlHttp.open("GET", url, true);
xmlHttp.onreadystatechange = callback;
xmlHttp.send(null);
} else {
clearProvince();
}
}
function createXMLHttpRequest(){
if(window.ActiveXObject){
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if(window.XMLHttpRequest){
xmlHttp = new XMLHttpRequest();
}
}
function callback(){
if(xmlHttp.readyState == 4){
if(xmlHttp.status == 200){
var name = xmlHttp.responseXML.getElementsByTagName("name")[0].firstChild.data;
setProvincie(xmlHttp.responseXML.getElementsByTagName("name"));
}
else if (xmlHttp.status == 204){
clearNames();
}
}
}
Queste sono le funzioni che preparano l’invio della richiesta e la inoltrano al server per poi occuparsi di rappresentarla correttamente al client. Sono le stesse funzioni che abbiamo già visto nell’articolo precedente: lì erano generiche, qui sono specializzate per l’esempio che si sta descrivendo.
c. La preparazione e l’invio della risposta del server, utilizzando una servlet:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
String prefix = request.getParameter("names");
NameService service = NameService.getInstance(names);
List matching = service.findNames(prefix);
if(matching.size() > 0){
PrintWriter out = response.getWriter();
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
out.println(""); ");
Iterator iter = matching.iterator();
while(iter.hasNext()){
String name = (String) iter.next();
out.println("" + name + " ");
}
out.println("
matching = null;
service = null;
out.close();
} else {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
Figura 1 – Autocompletamento
Come si può facilmente intuire non è molto comodo ne’ pratico dover scrivere tutto questo codice per effettuare una sola richiesta Ajax, soprattutto se si pensa che molto del codice appena visto è lo stesso, indipendentemente del tipo di scenario applicativo nel quale si vuole inserire Ajax. Proprio per questo motivo si sono evolute una serie abbastanza numerosa di librerie JavaScript che rendono tutta la serie di invocazioni Ajax trasparente al programmatore. In questo modo il compito del programmatore si riduce alla semplice organizzazione dei campi di input e alla preparazione della risposta nel formato corretto per essere interpretato da funzioni JavaScript messe a disposizione delle librerie. Tutto ciò è molto comodo perche’ permette ad un programmatore che già ha un po’ di esperienza nelle applicazioni client server tradizionali(per intenderci, per tradizionali si intendono le applicazioni che non fanno utilizzo delle tecnologie Ajax), di utilizzare questo nuovo pattern applicativo in maniera del tutto trasparente, ed attraverso una sola e semplice chiamata ad una funzione JavaScript.
Prima di analizzare come variano i tre passi precedenti con l’introduzione di Prototype e Script.aculo.us analizziamo brevemente queste librerie.
Prototype e Scripta.culo.us
Prototype[1] fornisce infatti un set di estensioni per JavaScript, per l’ambiente browser e per l’oggetto XMLHttpRequest; sulla base di questo framework Script.aculo.us[2] costruisce le proprie basi per fornire oggetti di interfaccia dalle funzionalità evolute come animazioni, drag-and-drop e altri.
In Prototype le funzionalità per Ajax sono contenute nell’oggetto globale Ajax, xmlHttpRequest si occupa invece del trasporto delle richieste Ajax, con differenze per i browser gestite internamente con sicurezza, senza l’interazione dell’utente.
Le richieste vengono inoltrare creando un’istanza dell’oggetto Ajax.Request:
new Ajax.Request('/some_url', { method:'post' });
Il primo parametro è l’URL della richiesta, mentre il secondo indica il metodo da utilizzare nella richiesta(di default è post).
Le richieste Ajax sono di default asincrone, per questo c’è la necessità di una funzione di callback che gestisca e organizzi i dati ricevuti al momento della risposta del server. Le funzioni di callback sono passate al momento della richiesta:
new Ajax.Request('/some_url', { method:'get', onSuccess: function(transport){
var response = transport.responseText
|| "no response text";
alert("Success! " + response); },
onFailure: function(){ alert('Something went wrong...') }
});
Qui ne sono passate due a seconda dello stato della risposta: onSuccess e onFailure.
Il primo parametro passato è l’oggetto nativo xmlHttpRequest dal quale si possono prelevare le proprietà responseText e responseXML. Si possono specificare entrambe le funzioni di callback, una o anche nessuna. Altre funzioni di callback disponibili sono: onUninitialized, onLoading, onLoaded, onInteractive, onComplete e onException.
A tutte corrisponde un determinato stato dell’oggetto di trasporto xmlHttpRequest, ad eccezione di onException che viene chiamata quando c’è un’eccezione nell’invio di un’altra funzione di callback.
Sono disponibili anche delle funzioni di callback chiamate onXXX, con XXX che rappresenta lo stato della risposta HTTP come ad esempio 200 o 404. Quando si utilizzano tali funzioni non vengono chiamate le funzioni onSuccess o on Failure poiche’ la funzione onXXX ha la precedenza, per cui è necessario utilizzarle con cautela e consapevolezza di ciò che si sta facendo.
Parametri e metodo HTTP
I parametri per la richiesta possono essere passati attraverso la proprietà parameters nelle opzioni:
new Ajax.Request('/some_url', {
method: 'get',
parameters: { company:'example', limit:12 }
});
I parametri possono anche essere passati come stringa di coppia di valori chiave separati da “&” (company=example&limit=12).
Si può usare parameters con entrambe le richieste GET e POST.
Una delle principali applicazioni per la proprietà parameters è l’invio di contenuti di una form con una richiesta Ajax, e Prototype fornisce un metodo di supporto per questo: Form.serialize:
new Ajax.Request('/some_url', {
parameters: Form.serialize('id_of_form_element')
});
Se si ha il bisogno di inserire headers personalizzati per le richieste HTTP, si può utilizzare l’opzione requestHeaders passando le coppie nome-valore o come hash o come un array piatto, del tipo: [‘X-Custom-1’, ‘value’, ‘X-Custom-2’, ‘other value’].
Se, per qualche ragione, si deve fare una richiesta POST con un corpo personalizzato, non con i parametri dell’opzione parameter, c’è l’opzione postBody esattamente per questa funzionalità.
Analizzare una risposta JavaScript
L’applicazione può essere progettata per inviare codice JavaScript nelle risposte, se il content type della risposta coincide con il MIME type di JavaScript. Prototype, tramite eval(), restituisce il codice automaticamente. Non c’è il bisogno di catturare la risposta esplicitamente.
Altrimenti, se la risposta contiene un X-JSON header, il contenuto sarà analizzato, salvato in un oggetto e passato al metodo di callback come secondo argomento:
new Ajax.Request('/some_url', { method:'get',
onSuccess: function(transport, json){
alert(json ? Object.inspect(json) : "no JSON object");
} });
Questa funzionalità può essere utilizzata quando si trattano dati non banali con Ajax, e si ha la necessità di evitare di sovraccaricare la risposta con l’analisi dell’XML. JSON è molto più veloce e leggero dell’XML.
Responders globali
L’oggetto Ajax.Responders si occupa di ogni richiesta Ajax. Con questo, si possono registrare i metodi di callback che vengono chiamati in base ad un determinato stato di una Ajax.Request:
Ajax.Responders.register({
onCreate: function(){
alert('a request has been initialized!');
},
onComplete: function(){
alert('a request completed');}
});
Ogni callback che coincide con lo stato di trasporto xmlHttpRequest è permessa, con l’aggiunta di onCreate. Tracciare richieste come queste può essere utile per molti motivi: si possono loggare per farne il debug utilizzando un logger JavaScript oppure fare una sorta di raccoglitore globale di eccezioni che informa l’utente di un possibile errore di connessione.
L’autocompletamento con Prototype
Torniamo allo scenario applicativo analizzato precedentemente e ripercorrendo i passi che ne costituiscono la struttura organizzativa osserviamo le differenze rispetto al codice di prima.
Ecco il corpo di una pagina HTML che contiene un campo di input per la richiesta (a) e le funzioni JavaScript che costituiscono il cuore di Ajax (b):
Script.aculo.us Auto Complete Sample
Script.aculo.us Auto Complete Sample
Provincia:
Viene prima di tutto inclusa all’interno del codice la libreria Script.aculo.us che, come si può osservare, va affiancata da Prototype.
Successivamente si predispone un campo di input identificato attraverso l’id “autocomplete” atto a ricevere le ricerche dell’utente.
Si dispone un layer identificato attraverso l’id “autocomplete_choices” che conterrà i risultati ottenuti dalla ricerca.
Infine si predispone la funzione Ajax.Autocompleter che si assumerà il compito di eseguire chiamate verso una servlet che incapsulerà le risposte date dalla query in un layer contenitore. Ecco i dettagli del codice della servlet che prepara e spedisce la risposta al client(c):
private void printResponse(HttpServletRequest request,
HttpServletResponse response, String res)
throws ServletException, IOException{
String prefix = request.getParameter("autocomplete");
NameService service = NameService.getInstance(names);
List matching = service.findNames(prefix);
if(matching.size() > 0){
PrintWriter out = response.getWriter();
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
out.println("
- ");
- " + name + " ");
Iterator iter = matching.iterator();
while(iter.hasNext()){
String name = (String) iter.next();
out.println("
}
out.println("
matching = null;
service = null;
out.close();
} else {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
Figura 2 – Autocompletamento con Prototype e Script.aculo.us
Si nota subito che il risparmio di codice è evidente, ora bastano due righe di JavaScript per mettere in moto il meccanismo dell’autocompletamento e senza dover in alcun modo realizzare le chiamate tipiche di un’invocazione Ajax.
Il codice completo (codice.zip) è scaricabile come allegato dal menu in alto a sinistra
Conclusioni
Abbiamo quindi notato che grazie ad una libreria JavaScript tutti possono scrivere applicazioni Ajax, poiche’ queste rendono tutte le invocazioni trasparenti al programmatore, liberandolo anche dallo sforzo di dover adattare il codice al browser utilizzato perche’ tale logica viene incapsulata appieno da queste librerie. Uno svantaggio derivante dal loro utilizzo può essere ricondotto alla dimensione che tali librerie effettivamente hanno e che possono portare ad un’appesantimento della pagina Web, di cui eventualmente tener conto in certe situazioni.
Riferimenti
[1] Prototype
http://www.prototypejs.org/
[2] Script.aculo.us
http://script.aculo.us/