Come abbiamo visto nel capitolo precedente, il cuore della tecnologia Ajax e’ il componente XMLHttpRequest, per il quale abbiamo visto un paio di tecniche di caricamento in memoria.
Abbiamo anche mostrato quali metodi ed attributi vengono forniti con esso, e introdotto il concetto di transazione asincrona tra browser e web server utilizzando il protocollo http.
A quest punto lasciatemi fare un una piccola analogia.
Abbiamo ora a disposizione una bella ruota, non resta che costruirci attorno un’ automobile. Dapprima vedremo come costruire una utilitaria, ovvero come circondare il componente asincrono con un motore semplificato che possa gestire le operazioni basilari che vogliamo implementare sulla nostra web application (l’Ajax Engine). Successivamente raffineremo l’Ajax Engine e lo completeremo con un modulo di interfacciamento con l’Application Server ed aggiungeremo altre librerie di supporto alla GUI per arrivare ad avere una bella macchina sportiva, attraente come aspetto e valida dal punto di vista prestazionale.
AjaxCore – L’Ajax Engine di base.
Una volta scritta la funzione loadXHR() , bisogna definire il set di funzioni che si occupano del vero e proprio caricamento asincrono dei dati utilizzando il metodo GET oppure POST messi a disposizione, a seconda che si richiedano dei dati al server oppure si voglia anche trasferire un set di parametri preliminari contestalmente alla richiesta. Di solito si omette di utilizzare il metodo PUT.
Quindi il nostro Ajax Engine potra’ contenere le due funzioni seguenti:
Queste due funzioni si occupano della richiesta di una pagina page - statica o dinamica - al web server, definendo un contenitore di destinazione dest dove poi dovremo smistare il risultato ottenuto eventualmente dall’esecuzione di questa pagina sul server , accompagnando la richiesta con un set di parametri pars per costruire la querystring completa da inviare nel caso di utilizzo del metodo POST.
Vediamo un po’ piu’ nel dettaglio il comportamento delle due funzioni.
loadHTMLDoc: dopo aver caricato, se occorre, l’oggetto HDR si crea l’”event handler” collegato all’evento onreadystatechange . Un event handler non e’ altro che l’operazione che deve essere eseguita ogni qualvolta il browser attiva questo evento (event fired) segnalando la variazione di stato dell’oggetto HDR.
Se quindi lo stato dell’oggetto HDR e’ cambiato si controlla quale e’ lo stato attuale, e nel piu’ semplice dei casi si prosegue solo quando (x.readyState == 4 && x.status == 200) , ovvero quando lo stato indica che si e’ ricevuta una risposta dal webserver, e tale risposta ha codice 200 – OK.
In questo caso si associa l’eventuale risposta (responseText o responseXML) all’elemento del Document Object Model passato come contenitore di destinazione per i dati richiesti.
ResponseText, responseXML e DOM
Una volta ricevuti i dati richiesti in modo asincrono al web server, come si “inglobano” nella pagina HTML correntemente visualizzata sul browser? In che formato devono essere trasmessi i dati dal web server?
Per rispondere a queste domande, e soprattutto per capire le scelte fatte nel nostro framework Ajax, occorre considerare il seguente aspetto: come si visualizza una pagina web sul browser?
Generalmente il browser legge il codice sorgente HTML della nostra pagina web inviatogli dal web server. Ne effettua il parsing e interpreta i tag html per formattare il documento e visualizzarlo all’interno della finestra del browser. Durante l’operazione di parsing viene costruita in memoria una struttura ad albero di quella che e’ la struttura interna al documento – il Document Object Model o DOM.
Il DOM non e’ altro quindi che la rappresentazione nella memoria della macchina client della struttura del documento visualizzato dal nostro browser.
Document Object Model (DOM) è una forma di rappresentazione dei documenti strutturati come modello orientato agli oggetti. DOM è lo standard ufficiale del W3C per la rappresentazione di documenti strutturati in maniera da essere neutrali sia per la lingua che per la piattaforma. DOM è inoltre la base per una vasta gamma delle interfacce di programmazione delle applicazioni; alcune di esse sono standardizzate dal W3C.
DOM è stato inizialmente supportato dai browser per modificare gli elementi in un documento HTML. DOM è stato un modo per accedere e aggiornare dinamicamente il contenuto, struttura e stile dei documenti. A causa delle incompatibilità nell'esecuzione di DOM fra i vari browser, il W3C ha fornito delle specifiche standard.DOM non mette limitazioni sulla struttura dei dati del documento. Con DOM, un documento ben formato può essere visto come un albero. La maggior parte dei parser XML (come Xerces) e i processori XSL (per esempio Xalan) sono stati sviluppati per usare la struttura ad albero. Questa implementazione richiede che l'intero contenuto di un documento venga analizzato e salvato in memoria. Quindi, DOM può essere usato molto bene per applicazioni dove gli elementi del documento devono essere trovati in modo random e modificati. Per le applicazioni basate su XML che usano un processo di lettura e scrittura per analisi, DOM presenta un grande spreco di memoria; per questo tipo di applicazioni si consiglia di usare il modello SAX.
Questo Document Object Model e’ accessibile al programmatore come struttura XML. A grandi linee l’albero del Document Object Model e’ composto da elementi chiamati Nodi.
· I Nodi identificano i vari elementi, o contenitori, della pagina html .
· I dati veri e propri della pagina, ovvero il contenuto degli elementi html sono rappresentati come attributi dei vari nodi.
Per modificare dinamicamente il contenuto di una pagina web occorre sostanzialmente identificare all’interno dell’albero del DOM il nodo o i nodi che contengono i dati che si vogliono modificare e “appiccicare” il nuovo contenuto al nodo preso come riferimento (parent node), togliendo eventualmente anche i sottonodi (child nodes) con i dati precedenti.
Ad esempio se vogliamo modificare il contenuto di una cella che costituisce una tabella html, occorre modificare il testo racchiuso nei tag
Bisogna cioe’ recuperare un riferimento o “reference” della cella/nodo in questione, e cambiare il contenuto interno, sovrascrivendo quello precedente.
Il modo piu’ semplice per sostituire il contenuto della cella in modo dinamico e’ quello di definire il nuovo contenuto valorizzando l’attributo innerHTML del nodo interessato.
Non appena il comando viene interpretato dal browser si ha il refresh automatico di quanto si sta visualizzando sullo schermo, e viene mostrato il nuovo contenuto della cella modificata all’interno della tabella visualizzata nella nostra pagina html caricata, senza doverla ricaricare da zero.
Nel caso di modifiche piu’ complesse si puo’ definire direttamente la struttura del codice html che deve diventare il nuovo valore dell’attributo innerHTML del nodo di riferimento, oppure si puo’ costruire direttamente la struttura XML dei nuovi nodi che devono far parte del DOM modificato.
La prima strada presuppone che il nuovo dato ci venga fornito nativamente come codice html preformattato, mentre la seconda strada lascia una liberta’ maggiore sul formato che devono avere i nuovi dati nel caso vengano forniti da terze parti, magari recuperandoli in remoto da un database server.
Il vantaggio del primo metodo e’ una semplicita’ intrinseca nell’inserimento dei nuovi dati all’interno del DOM preesistente (si valorizza l’attributo innerHTMLdel nodo padre), il vantaggio del secondo e’ una maggiore liberta’ rispetto a fornitori di dati esterni che possono utilizzare il formato XML o altri formati per l’interscambio dei dati, come il formato JSON.
Nel nostro framework semplificato abbiamo optato per mantenere inizialmente una elevata semplicita’ di integrazione della nuova tecnologia all’interno dell’applicazione. Grazie a cio’ la progettazione dei fornitori dati attivati sul webserver rimane pressoche’ invariata rispetto alla normale progettazione di pagine HTML/JSP/ASP.NET, a meno di piccole variazioni che vedremo piu’ avanti nel dettaglio.
Quindi l’impostazione con cui si opera utilizzando i vari fornitori dati e’ la seguente:
- si individua il punto destinazione all’interno del DOM
- si richiama il fornitore dati sul webserver, solitamente una pagina dinamica, a volte un file html preformattato
- si attiva la funzione loadHTMLDoc o loadHTMLPost a seconda del metodo GET o POST di trasmissione necessaria.
Resta ora da capire come individuare all’interno del Document Object Model il punto di inserimento dei dati restituiti dal web server in modo asincrono.
Referenziazione di uno o piu’ elementi all’interno del Document Object Model
Il linguaggio JavaScript fornisce tre metodi principali per ottenere un reference ad uno o piu’ elementi all’interno del DOM ricostruito in memoria:
- document.getElementById();
- document.getElementsByTagName();
- document.getElementsByName();
Tutti e tre i metodi sono metodi associati all’oggetto document, elemento principale del nostro DOM, che rappresenta la nostra pagina web.
Il primo metodo restituisce un reference ad un elemento singolo identificato dall’attributo html ID=”…” all’interno di un Tag. Tale attributo deve essere unico all’interno di tutto il documento. Se cosi’ non avviene si ottiene un reference non valido.

Il secondo metodo getElementsByTagName(“

Il terzo metodo getElementsByName e’ l’analogo del secondo, ma punta non agli elementi con un TAG specifico, ma con un attributo HTML NAME=”…” identico. Allo stesso modo del metodo precedente ritorna quindi un array contenente i riferimenti a tutti gli elementi con un dato valore dell’attributo NAME.

Per concludere, molto spesso si utilizza una combinazione di questi tre metodi per recuperare i reference degli oggetti desiderati, raggruppati all’interno del nostro documento sfruttanto gli attributi NAME e i TAG.
Il piu’ delle volte comunque si ha necessita’ di recuperare il reference ad un singolo elemento, per cui nel nostro framework, per ottimizzare le dimensioni del sorgente Javascript, e’ stata implementato un wrapper dedicato:

Passaggio di parametri al Web Server e chiamata di pagine dinamiche lato server.
Resta ora da risolvere un altro punto importante all’interno dell’architettura delle applicazioni web tradizionali: come facciamo a richiamare funzioni remote sul web server per effettuare ad esmpio l’inserimento di dati sul Database che contiene i dati mostrati e modificati sul nostro client?
Da quanto analizzato in precedenza si deve utilizzare una variante della funzione loadHTMLPost, per poter passare tutti i parametri col metodo POST. Nel nostro framework aggiungeremo una funzione getBeanResult ed un wrapper setValToDB.
Innanzitutto si e’ scelto di differenziare il metodo base loadHTMLPost per fornirgli maggiori potenzialita’, includendo anche un parametro con cui indicare diversi metodi alternativi nel caso in cui dovessimo trattare in maniera diversa i dati restituiti dal server. Il wrapper serve per semplificare la chiamata del metodo sottostante ottimizzandone le dimensioni del codice.
Stiamo ipotizzando di dover attivare un file getBean.jsp, che utilizza le Java Server Pages per poter interagire con un DB server remoto tramite una libreria di funzioni richiamabili traite il meccanismo dei JavBeans.


Il wrapper nasce dal fatto che la chiamata diretta al metodo sottostante puo’ essere scomoda da usare a causa della complessita’ di formato nel caso particolare in cui occorra inserire dati sul Database, mentre il wrapper ha un formato decisamente piu’ standard:

In questo modo possiamo richiamare una pagina dinamica scritta in JSP passando tutta una serie di parametri costruendo una querystring opportuna. Il risultato ottenuto, ovvero il codice html generato dall’application server, puo’ servire per modificare un attributo all’interno di un nodo, un nodo completo o un insieme di nodi all’interno del nostro Document Object Model.
Come si attiva l’Ajax Engine
Una volta capito come richiedere i dati al web server e come smistarli all’interno della nostra pagina all’interno del browser, ci resta da comprendere come integrare il nostro Ajax Engine semplificato scritto in Javascript all’interno della nostra applicazione.
L’applicazione di codice Javascript ad una pagina web e’ una operazione molto semplice; tutto cio’ che occorre e’ usare il tag seguente:
<#script type="text/javascript">
// codice vero e proprio dello script
<#/script>
Per i browser piu’ datati, oppure se vogliamo utilizzare lo XHTML (la nuova versione dell’html) al posto di quello solito, abbiamo bisogno di commentare il codice per avere la sicurezza che il browser non lo mostri all’interno della pagina o cerchi di interpretarlo come tag html.
Esistono die diversi stili per commentare il codice. Per i documenti html e per quelli xhtml in stile “transitional” si usano i normali commenti html:
<# script type="text/javascript">
<#!--
// codice vero e proprio dello script
--#>
< #/ script>
Per I documenti scritti usando l’ xhtml secondo lo stile “strict” occorre usare la sintassi di commento CDATA. percio’ solitamente in questa tipologia di documenti si preferisce utilizzare codice Javascript solo all’interno di file di scripting a parte.
In questo caso si inserisce all’interno della sezione ... della pagina web il riferimento al file esterno.

Resta da capire, una volta che sappiamo come caricare il file esterno con il nostro script, come far interagire lo script con la pagina web, ovvero in che modo attivarlo.
Finora, nel web 1.0, ci sara’ capitato di scrivere codice javascript in relazione al metodo submit() di un form. In quel caso l’azione scatenata dal metodo richiamato dal form richiamava le funzioni javascript che eventualmente avevamo scritto per gestire la validazione dei dati interni al form, oppure per effettuare una prima fase di trasformazione dei dati stessi prima di inviarli al wb server.
Per attivare il nostro Ajax Engine sfrutteremo il seguente meccanismo implementato nel nostro browser per segnalare l’avvenuto caricamento iniziale della nostra pagina web. Il browser riceve dal web server il codice html della nostra pagina; costruisce in memoria il suo DOM a mano a mano che effettua il parsing dell’html, carica in memoria anche gli evetuali fogli di stile esterni, ed il nostro ajax engine.
Quando questa serie di operazioni termina, prima di effettuare il rendering della pagina, ovvero la visualizzazione del contenuto sul pannello principale del browser, viene lanciato un avviso di avvenuto caricamento, intercettabile tramite un event handler Javascript collegato all’evento onLoad().
Oppure possiamo anche sfruttare una caratteristica propria del caricamento del file javascript esterno. Il codice in esso contenuto viene caricato nella memoria del browser ed immediatamente parserizzato e caricato in memoria se si tratta di variabili o funzioni oppure eseguito se si tratta di una invocazione ad una funzione.
Il primo approccio subordina l’esecuzione del codice javascript ad un evento specificamente conseguente al caricamento della nostra pagina web, mentre il secondo presuppone l’esecuzione del codice non appena il browser ha questa possibilita’. Puo’ anche darsi prima dell’attivazione dell’evento onLoad(). Sinceramente penso dipenda dai meccanismi interni ai vari browser. se vi interessa potrete verificare sulla documentazione rilasciata con i vari browser per vedere quale meccanismo usino.
Il punto importante secondo me e’ che se siamo costretti a subordinare l’esecuzione del nostro codice javascript solo dopo il caricamento di tutti gli elementi della nostra pagina dobbiamo utilizzare la prima tecnica. La seconda e’ utile in tutti gli altri casi, non avendo il vincolo di attivare quell’event handler. All’inizio consiglio l’approccio piu’ prudente, utilizzando l’evento onLoad(), anche per evitare noiosissimi problemi di sincronizzazione tra codice javascript e pagina web, assai ostici da individuare e risolvere.
Attenzione alla Cache
Normalmente la cache del nostro browser ci torna utile. Il browser memorizza i file caricati, cioe’ l’utente non deve caricare ogni volta gli script. Pero’ nell’ approccio Ajax questa tecnica di caching puo’ causare dei problemi.
Il browser Safari e’ quello piu’ pericoloso per noi, perche’ almeno nelle versioni precedenti memorizzava lo status della risposta ottenuta dal web server e non segnalava piu’ le sue variazioni.
Le ultime versioni di questo browser potrebbero non avere piu’ questo comportamento specifico.
Comunque evitare questi problemi di caching e’ relativamente semplice: prima di chiamare il metodo send() nel componente XMLHttpRequest e’ sufficiente aggiungere una intestazione specifica alla richiesta che stiamo effettuando con quel componente.
Questa intestazione ( o header ) segnala al browser la necessita’ di testare se la data e’ variata rispetto ad una data di riferimento. Basta inizializzare questa data di riferimento su una data passata al momento in cui scriviamo il codice:

Riassunto
Abbiamo visto come progettare una piccola libreria JavaScript che ci permette di effettaure richieste HTTP in modo asincrono direttamente al web server per richiedere dati generati in modo statico oppure con una pagina web dinamica senza dover pero’ ricaricare la pagina correntemente visualizzata sul browser. Abbiamo anche compreso come ridistribuire i dati richiesti e ricevuti all’interno del documento visualizzato e come attivare questa libreria all’interno di un documento html. Il vantaggio che immediatamente ci salta all’occhio e’ dato dal fatto di non dover ricaricare la maggior parte di una pagina web nel caso avessimo bisogno di aggiornare solo piccole porzioni se non un singolo dato visualizzato nella nostra interfaccia grafica sul client. L’aggiornamento di pochi dati generalmente e’ pressoche’ istantaneo, riducendo solitamente a pochi bytes il codice trasmesso dal server al client. Anche per dati piu’ corposi di solito l’utente continua a lavorare con la sua applicazione web senza i tempi morti e lo sfarfallio che avviene sul browser dovuti al ricaricamento della pagina completa che si avevano nelle applicazioni tradizionali.