Per approfondire meglio questi concetti analizzeremo nel dettaglio una piccola applicazione di test che fa riferimento all'esempio teorico visto nella prima parte e utilizza la libreria LazyLoader. Come ho detto in precedenza la libreria dipende da Prototype e quindi è necessario includere anche questa dipendenza nel nostro progetto. Nonostante questo, non ho fatto uso di questa libreria (se non in un unico caso specifico, poi si capirà il perché) proprio per non creare confusione.
L'architettura è molto semplice. L'applicazione presenta tre tab dedicati ognuno ad una specifica funzionalità: gestione dei contatti, calendar e filesystem remoto. Il comportamento di ciascuna di queste funzionalità è inserito in file specifici; avremo quindi contacts.js per la rubrica contatti, calendar.js per il calendario degli eventi e filesystem.js per il file manager remoto. Ecco comunque l'esempio.
Questi file ovviamente presentano lo stesso namespace e implementano tutti la stessa interfaccia per poter essere invocati dinamicamente.
Oltre a questi file è presente un file (init.js) che inizializza l'applicazione in se e permette di scaricare in maniera lazy i file specifici per i tab e di invocare il loro metodo di inizializzazione.
Iniziamo ad analizzare i file index.html e init.js.
<html>
<head>
<script src="lib/prototype.js" type="text/javascript"></script>
<script src="lib/lazyloader.js" type="text/javascript"></script>
<script src="init.js" type="text/javascript"></script>
<link rel="stylesheet" href="style.css"/>
<script>window.onload = myApp.init;</script>
<body></body>
</html>
Il file index.html è quasi banale: vengono caricate le dipendenze (Prototype e lazyloader), il file init.js che contiene l'inizializzazione della mini applicazione, il file CSS e viene associata la funzione myApp.init
come callback dell'evento onload
.
myApp = {};
myApp.init = function() {
var ul = document.createElement("UL");
ul.id = "tab";
var tabs = ["contact", "calendar", "filesystem"];
for(var i = 0; i<tabs.length; i++) {
var li = document.createElement("LI");
li.innerHTML = tabs[i].toUpperCase();
li.id = "tab_"+tabs[i];
li.onclick = myApp.changeTab.bind(this, tabs[i]);
ul.appendChild(li);
}
document.body.appendChild(ul);
var content = document.createElement("DIV");
content.id = "content";
document.body.appendChild(content);
myApp.changeTab(tabs[0]);
}
myApp.changeTab = function(tab) {
var tabEl = document.getElementById("tab_"+tab);
tabEl.className = "selected";
if(myApp.currentTab) myApp.currentTab.className = "";
myApp.currentTab = tabEl ;
var content = document.getElementById("content");
LazyLoader.load("tabs/"+tab+".js", function() {
while(content.firstChild) content.removeChild(content.firstChild);
myApp[tab].init(content);
});
}
Il file init.js, dopo aver inizializzato il namespace myApp
, definisce due semplici funzioni.
Il metodo init
non fa nient'altro che definire dinamicamente la struttura della pagina creando una lista ciclando il vettore tabs
e un div con id="content"
che rappresenta il contenuto di ciascun tab.
Da notare l'unico utilizzo di Prototype all'interno del mini esempio: l'utilizzo della funzione bind
. Essa permette di modificare lo scope e i parametri passati ad una funzione proprio come avevamo visto nel precedente articolo dedicato proprio allo scope e all'utilizzo di metodi come callback di eventi.
Bind
permette di creare una nuova funzione che deriva da myApp.changeTab
ma che viene eseguita avendo come parametro tabs[i]
evitando quindi problemi derivanti dall'assegnazione di una funzione all'interno di un ciclo.
Per ultimo viene invocato changeTab
passando come parametro il nome del primo tab per inizializzarlo in automatico all'avvio.
Il metodo changeTab
(che risponde ad ogni click sui tab) si occupa di cambiare la classe CSS del li
selezionato e invoca il metodo load
della libreria LazyLoading passando come parametro il nome del file .js da scaricare ed una funzione di callback che verrà invocata al termine del download.
Questa funzione inline, dopo aver rimosso qualsiasi elemento precedente contenuto nel div content, esegue il metodo init
del tab attivato passando come parametro un riferimento all'elemento HTML da riempire con le informazioni relative. Guardando il codice capiamo subito che ciascun tab dovrà implementare il metodo myApp.nomeTab.init
in quanto esso sarà invocato in maniera automatica una volta scaricato il file.
Analizziamo ora la struttura di filesystem.js in quanto rappresenta il tab più complesso; gli altri sono di banale comprensione.
myApp.filesystem = {};
myApp.filesystem.data =
[{ name:'Documenti',
items: [{ name: 'Curriculum.odt' },
{ name: 'Fatture.odt' }] },
{ name:'Guide HTML',
items: [{ name:'Tecniche Javascript',
items: [{ name: 'Lo scope in JS' },
{ name: 'Le closure' }]
}]
}]
myApp.filesystem.init = function(content) {
content.appendChild(myApp.filesystem.createUl(myApp.filesystem.data));
}
myApp.filesystem.createUl = function(roots) {
var ul = document.createElement("UL");
for(var i = 0; i<roots.length; i++) {
var li = document.createElement("LI");
li.appendChild(document.createTextNode(roots[i].name));
if(roots[i].items)
li.appendChild(myApp.filesystem.createUl(roots[i].items));
ul.appendChild(li);
}
return ul;
}
Innanzitutto è necessario inizializzare il namespace relativo al tab (myApp.filesystem
) e definire alcuni dati di test da mostrare (myApp.filesystem.data
).
Successivamente viene implementato il metodo init
(che ricordo essere obbligatorio per ciascun tab) il quale al suo interno non fa nient'altro che invocare un altro metodo (myApp.filesystem.createUl
) ed appendere il suo risultato al div ricevuto come parametro da init.js.
Il metodo createUl è un metodo ricorsivo che si occupa di costruire una struttura ad albero a partire da un vettore di nodi. Nel caso infatti che un nodo abbia dei nodi figli (nel nostro caso si utilizza la proprietà items
) viene invocata ricorsivamente la stessa funzione. In questo modo il metodo torna l'oggetto UL formattato.
Conclusioni
Dalle lezioni abbiamo sicuramente apprezzato il Lazy Loading come una tecnica per migliorare nettamente le performance di una applicazione web moderna. Utilizzando questa tecnica sussiste però l'esigenza di creare una struttura comune alle varie funzionalità (nel nostro piccolo esempio il file init.js) che permetta non solo di scaricare i nuovi file "a comando" ma anche di garantire quella dinamicità che le applicazioni web necessitano. Una struttura di questo tipo non è sicuramente di facile realizzazione forse più dal punto di vista progetturale che dello sviluppo.
Come al solito l'esempio, pur banale che sia, è riuscito a cogliere i vantaggi e le criticità di questo approccio in maniera semplice e di facile comprensione.†
Per ultimo alleghiamo i sorgenti dell'applicazione.