Uno degli ostacoli che possono frenare lo sviluppo di una qualsiasi applicazione da parte di sviluppatori inesperti è spesso la mancanza di conoscenza riguardo al “cosa” deve essere inviato dal server una volta che la pagina è stata caricata. Siamo tutti d'accordo affermando che una tecnologia come AJAX possa trasformare un semplice sito web in una vera applicazione con un senso di usabilità e di integrazione pari ad un applicativo desktop, ma questo non basta a rispondere a tutti i dubbi.
Come devo organizzare l'applicazione? È meglio mettere a disposizione del client una quantità tale di informazioni da permettere di eseguire alcune funzionalità senza l'intervento del server o è da preferire una maggiore quantità di richieste remote? In che formato devo inviare le informazioni?
Rispondere a queste domande non è propriamente un'attività banale, soprattutto perché non esiste una risposta migliore in assoluto, ma tutto dipende dal contesto nel quale l'applicazione verrà eseguita una volta realizzata e dalle risorse e dal tempo a disposizione.
In questo articolo analizzeremo i tre principali (e forse gli unici) pattern di comunicazione che possono essere implementati in un'applicazione che fa uso di AJAX. Con pattern di comunicazione intendiamo proprio il “cosa” deve essere inviato dal server ad ogni richiesta. Ciascun pattern presenterà aspetti positivi e fattori critici che dovrebbero essere valutati in base alla tipologia di software che si intende realizzare. Per ciascuna metodologia di sviluppo verrà allegato un piccolo esempio che fornirà alcune soluzioni dal punto di vista implementativo. Gli esempi utilizzeranno come libreria a supporto delle richieste la famosissima Prototype, nella sua ultima versione stabile, la 1.6.
È ovviamente buona pratica quella di utilizzare lo stesso pattern per l'intero ciclo di vita dell'applicazione e non implementare diversi pattern contemporaneamente. Questo per avere un minimo di sintonia all'interno dell'applicazione: fattore rilevantissimo soprattutto in un'ottica di manutenibilità e sviluppo in team.
Il content-centric pattern
Il primo pattern che analizzeremo è quello più facile da implementare e per questo rappresenta la tecnica più abusata soprattutto su internet.
Con questo approccio ad ogni richiesta il server risponde con un frammento di HTML che verrà inserito nella pagina corrente dal client tramite la proprietà innerHTML
posseduta da qualsiasi elemento della pagina. Il client quindi ignora tutto il layer business dell'applicazione e si occupa semplicemente di mostrare all'utente ciò che è stato ricevuto dal server.
Spesso questo approccio viene accompagnato dalla presenza di IFRAME nella pagina il cui contenuto viene aggiornato da un piccolo script JavaScript.
Vantaggi e contesti applicativi
Il content-centric pattern è tra i tre pattern descritti quello più facile da implementare in quanto presenta davvero pochissime righe di codice. Si adatta perfettamente in quei piccoli siti web che vogliono sorprendere l'utente con elementi di dinamicità non ancora frequenti sul web e che possono senza dubbio favorire l'usabilità. Estendere le funzionalità di partenza è senza dubbio un compito rapido e senza rischio per gli aspetti già implementati: basta semplicemente creare un nuovo file sul server e richiamare la procedura di aggiornamento dell'elemento HTML sul client.
Svantaggi e limitazioni
La facilità d'uso e la rapidità implementativa non possono che limitare altri aspetti troppo spesso non trascurabili. Con questa tecnica non è infatti possibile disaccoppiare la componente di presentazione da quella che gestisce i dati in quanto tutto è “mischiato” all'interno dei file presenti sul server. Questo limite fa sì che questo pattern non possa essere utilizzato in applicazioni di una certa entità. Inoltre dal punto di vista dell'usabilità si rischia di creare continui effetti di “blinking” per i quali alcuni frammenti della pagina vengono continuamente distrutti e ricostruiti da zero.
L'esempio
Per implementare questo pattern faremo affidamento all'oggetto statico Ajax.Updater presente nelle API di Prototype che si occupa proprio di aggiornare un elemento HTML a partire da un url.
La funzione esposta dalla libreria deve ricevere tre parametri: l'elemento che conterrà il nuovo frammento HTML, l'url al quale effettuare la richiesta e un oggetto JavaScript da utilizzare come insieme di opzioni. La funzione presenta ulteriori aspetti per esempio la possibilità di avere un doppio elemento HTML per gestire gli errori o quella di inviare particolari parametri insieme alla richiesta HTML. Per ulteriori informazioni riguardo al suo funzionamento non posso fare altro che suggerire una breve lettura delle API ufficiali disponibili a questo indirizzo: http://www.prototypejs.org/api/ajax/updater.
Un particolarità collegata a questo metodo riguarda la funzione Ajax.PeriodicalUpdater
che permette di effettuare refresh della pagina in maniera automatica in base ad un timeout impostabile (eventuali parametri sono impostabili come semplici opzioni. Le API di questa seconda funzione sono disponibili qui: http://www.prototypejs.org/api/ajax/periodicalUpdater.
Passiamo ora al nostro esempio. La pagina è molto semplice: presenta una lista di link e un div che rappresenta il container delle pagine. Ad ogni click viene invocata la funzione getContent
che non fa nient'altro che richiamare la funzione esposta da Prototype impostando in maniera corretta i parametri.
Ecco il brevissimo codice:
<html> <head> <title>Content-centric AJAX pattern</title> <script src="../prototype-1.6.js" type="text/javascript"></script> <script type="text/javascript"> function getContent(pageName) { var url = "server/"+pageName+".html"; new Ajax.Updater('content', url, {}); } </script> </head> <body> <ul id="menu"> <li><a href="#" onclick="getContent('page-1'); return false">Page 1</a></li> <li><a href="#" onclick="getContent('page-2'); return false">Page 2</a></li> <li><a href="#" onclick="getContent('page-3'); return false">Page 3</a></li> </ul> <div id="content"> Questa è la home page. </div>
Basterà ora inserire i contenuti all'intero dei file server/page-N.html per avere un mini sito web dinamico e agile.
Lo script-centric pattern
Lo script-centric pattern permette al client di ricevere direttamente dal server una porzione di codice JavaScript che verrà eseguita in maniera istantanea e automatica dal browser web.
Anche in questo caso lo sviluppo del client è abbastanza semplice in quanto faremo affidamento alla funzione JavaScript eval()
che permette di codice a partire da una stringa. Per i meno esperti che non conoscono la funzione eval
ecco un brevissimo esempio:
alert(“hello world”); eval(“alert('hello world')”);
Le precedenti righe eseguiranno esattamente la stessa funzione. La differenza che la prima invoca direttamente alert
mentre la seconda passa ad eval una stringa contenente il comando (attenzione ad eventuali apici singoli e doppi!). La funzione eval
è senza dubbio comoda, ma non dobbiamo abusarne in quanto richiede maggior numero di risorse rispetto all'invocazione diretta del metodo.
Vantaggi e contesti applicativi
Questa tecnica comunicativa è senza dubbio quella che permette una maggior estendibilità dell'applicazione in quanto per aggiungere nuove funzionalità spesso è necessario solamente modificare una componente server side, quella che si occupa di inviare codice JavaScript, mentre dal punto di vista del client tutto può rimanere esattamente come prima.
Il pattern infatti viene spesso utilizzato in quelle applicazioni che presentano diverse funzionalità tra di loro molto differenti (proviamo a pensare alla classica applicazione organizzata a tab, dove ogni tab ha una propria interfaccia grafica, un proprio insieme di oggetti da gestire e alcune funzionalità proprie non condivisibili con gli altri tab). Utilizzando lo script-centric pattern, ogni componente dell'applicazione avrà una vita propria e sarà facilmente gestibile. Inoltre un eventuale aggiunta di tab non presenta nessuna difficoltà implementativa dal punto di vista del client: basterà richiedere il nuovo script al server.
Lo script-centric pattern dovrebbe essere utilizzato in applicazioni web che si distaccano in qualche modo dal semplice sito web e che presentano aspetti più enterprise e di gestione dati, rispetto alla semplice presentazione di informazioni.
Spesso questo pattern viene accompagnato da tecniche di LazyLoading in quanto i file che vengono scaricati durante l'applicazione sono e veri propri script JavaScript che possono essere inclusi nella pagina anche attraverso la modifica del DOM della pagine utilizzanto i tag <script>
.
Svantaggi e limitazioni
Il principale svantaggio di questo approccio è la mancanza di suddivisione dei compiti tra client e server. Infatti come abbiamo detto prima, il client non conosce niente dell'applicazione in quanto riceve ogni volta i “comandi” dal server. Cosi facendo però all'interno della componente server-side sono presenti sia aspetti strettamente relativi a funzioni server (come per esempio la connessione a database) sia aspetti relativi all'interfaccia grafica. In questo modo si crea un po' di confusione, cattiva amica in caso di applicazioni di grandi dimensioni.
Inoltre l'utilizzo massiccio della funzione eval
non è molto consigliato per le sue carenze dal punto di vista delle performance e dell'utilizzo delle risorse.
L'esempio
Il piccolo esempio riguardante lo script-centric pattern è un'estensione di quello analizzato nel precedente articolo di Tecniche JavaScript riguardante il Lazy Loading. Come detto in precedenza infatti questo approccio può essere in qualche modo associato con la tecnica del Lazy Loading in quanto in entrambi vengono scaricati a run-time degli script da eseguire.
Per chi non avesse letto il suddetto articolo, l'esempio proponeva una applicazione a tab nella quale erano presenti tre sotto-applicazioni: un calendar, un gestore di contatti e un file system remoto. Rispetto all'esempio precedente, in questo articolo non utilizzeremo la precedente libreria di Lazy Loading in quanto non riguarda le tematiche affrontate, ma viceversa faremo uso di alcune API esposte da prototype.
Analizziamo l'index.html:
<html> <head> <script src="../prototype-1.6.js" type="text/javascript"></script> <link rel="stylesheet" href="style.css"/> <script> function executeScript(scriptName) { var url = "server/"+scriptName+".js"; new Ajax.Request(url, { method: 'get', onSuccess: function(transport) { var script = transport.responseText; eval(script); } }); } </script> <body> <ul id="tab"> <li onclick="executeScript('contact')">CONTACT</li> <li onclick="executeScript('calendar')">CALENDAR</li> <li onclick="executeScript('filesystem')">FILESYSTEM</li> </ul> <div id="content"></div> </body> </html>
La pagina iniziale è molto simile alla precedente tranne per il fatto che viene invocata la funzione Ajax.Request
(http://www.prototypejs.org/api/ajax/request) al posto dell'Updater passando come parametro un oggetto di opzioni tra le quali la funzione di callback onSuccess
che rappresenta il cuore dell'applicazione. Qua infatti è presente la funzione eval che esegue il contenuto della risposta (accessibile tramite transport.responseText
).
Analizziamo ora, per una comprensione completa, anche uno dei tre script che compongono l'esempio: calendar.js
var calendar = {}; calendar.data = [{ title:'Compleanno Anna', date: '6/12' },{ title:'Riunione capo', date: '12/12', priority: true },{ title:'Gita al mare', date: '15/12' }] calendar.init = function(content) { content.update(); for(var i = 0; i<calendar.data.length; i++) { var data = calendar.data[i]; var div = new Element("div", {className:'calendar'} ).update("<b>"+data.title+"</b><br/>"+data.date+"<br/>"); if(data.priority) div.addClassName("priority"); content.appendChild(div); } } calendar.init($('content'));
Dopo aver inizializzato i dati, viene definita la funzione init
che, grazie ad alcune API esposte da prototype (l'oggetto Element in primis), modifica l'elemento passato come parametro. Per ultimo invochiamo la funzione appena creata sul div con id content.
Grazie ad eval ad ogni click sul tab, viene eseguita la funzione init
.
Un'architettura server side di questo tipo è sicuramente adatta per scopi didattici ma è sconsigliata per ambienti di produzione soprattutto per la mancanza di un controllo che impedisca di riscaricare gli script a click successivi. Una possibile soluzione per ovviare a questo problema potrebbe essere quella di controllare eventuali download precedenti degli script e forzare la richiesta (tramite Ajax.Request) solamente se non presente in cache.
Il data-centric pattern
L'ultimo pattern relativo alle tecniche di comunicazione AJAX è il data-centric pattern. In questo approccio sono solamente i dati a viaggiare da server a client in quanto quest'ultimo conosce a priori le modalità di presentazione senza doverle ricevere dal client. I dati possono viaggiare in diversi formati per esempio JSON, SOAP o XML, ma anche in formati proprietari.
Rispetto ai precedenti due pattern, il data-centric necessita di un client “intelligente”, che conosce le logiche di business dell'applicazione e che sappia funzionare a prescindere dal server. Proprio per questo motivo questo approccio si adatta principalmente a grosse applicazioni, magari funzionanti anche attraverso intranet locali.
Vantaggi e contesti applicativi
Grazie alla presenza di client ricchi di funzionalità le diverse componenti dell'applicazione rimangono bene separate tra di loro. Per far riferimento al noto modello Model-View-Controller, utilizzando un data-centric pattern, le viste sono implementate totalmente client-side, mentre i controller server-side. I modelli dei dati invece rappresentano la componente che effettivamente viaggia attraverso la rete per raggiungere il client.
Questa suddivisione precisa dei compiti garantisce inoltre la possibilità di sostituire totalmente una delle due parti dell'applicazione (client e server) senza in nessun modo modificare la struttura dell'altra parte. Una volta infatti che è stato definito il formato di dati utilizzato è possibile utilizzare sia un client web realizzato in HTML e JavaScript, ma anche una classica applicazione desktop che legge i dati da un socket aperto puntando il server web.
Come detto in precedenza questo modello di comunicazione si adatta principalmente a grosse realtà enterprise orientate ai servizi che necessitano frequenti scambi di dati e soprattutto intercambiabilità dei componenti.
Svantaggi e limitazioni
Se da un lato questo approccio presenta notevoli vantaggi, dall'altro necessita di tempi di sviluppo sicuramente superiori rispetto ai precedenti. Diventa necessario infatti dotarsi di due applicazioni distinte e non più di un piccolo client che non fa altro che ricevere direttive e di server.
Nonostante dal punto di vista implementativo questa sia la soluzione più corretta, non sempre è attuabile.
L'esempio
Realizzare un esempio completo utilizzando il data-centric pattern è un compito abbastanza complesso per essere trattato all'interno di un articolo con un focus più generale: si rischierebbe infatti di spostare l'attenzione su un argomento secondario tralasciando quello principale, che rimane comunque l'analisi e il confronto di alcune tecniche di sviluppo.
Per chi volesse comunque affrontare l'argomento, suggerisco la lettura di alcuni mie precedenti articoli pubblicati su HTML.it, i quali approfondiscono la realizzazione di applicazioni AJAX che utilizzando proprio questo pattern di invio delle informazioni. Ecco alcuni link:
Conclusioni
Nell'articolo abbiamo introdotto tre diverse possibilità per implementare applicazioni distribuite che necessitano di frequenti comunicazioni tra client e server. I pattern sono stati descritti in ordine, dal più semplice ma meno organizzato, al più complesso ma più gestibile.
Come al solito gli esempi analizzati non rappresentano situazioni reali sia dal punto di vista grafico sia da quello funzionale, ma presentano in maniera comprensibile a tutti i contenuti teorici dell'articolo.
In questa tabella conclusiva vengono riassunti i fattori critici di ciascun pattern:
Pattern |
Contesti applicativi |
Vantaggi |
Svantaggi |
Content-centric pattern |
Siti web dinamici semplici e con poche funzionalità |
Banale da implementare e facilmente integrabile in applicazioni già esistenti |
Poco manutenibile in quanto i dati vengono mischiati con i componenti di presentazione |
Script-centric pattern |
Siti web e applicazioni di medie dimensioni con diverse funzionalità esposte |
Possibilità di estendere l'applicazione aggiungendo funzionalità senza alterare la componente client-side |
Troppa centralità del server nella gestione dei dati |
Data-centric pattern |
Applicazioni web in grado di gestire molti dati e intercambiabili |
Intercambiabilità delle componenti e migliore organizzazione del codice |
Lunghi tempi di sviluppo e relativi costi. |
Prima di salutarvi, ecco il link dove potete trovare i sorgenti.