Organizzazione del codice
Indice
Concetti chiave
- Il codice deve essere suddiviso in unità funzionali – moduli, servizi, ecc. E devono evitare la tentazione di avere tutto in un unico blocco $(document).ready(). Questo concetto è noto come incapsulamento.
- Non ripetere il codice. Identificare pezzi simili e utilizzare tecniche di eredità.
- Sebbene la natura di jQuery, non tutte le applicazioni JavaScript si trovano nel DOM e non tutte le parti di funzionalità hanno la necessità di avere un rappresentanzio nel DOM.
- Le unità di funzionalità devono avere un giunto flessibile (in inglese loosely coupled) – una unità di funzionalità dovrebbe essere in grado di esistere da sé e la comunicazione con altre unità dovrebbe avvenire attraverso un sistema di messaggi come eventi personalizzati o pub/sub. E per quanto possibile, tenere lontana la comunicazione diretta le unità funzionali.
Il concetto di giunto (o articolazione) flessibile può essere particolarmente problematico per gli sviluppatori che fanno la loro prima incursione in applicazioni complesse, quindi se siete di partenza, cercate di essere consapevoli di questo concetto.
L'Oggetto Letterale
10.1: Un oggetto letterale
var myFeature = { myProperty : 'ciao', myMethod : function() { console.log(myFeature.myProperty); }, init : function(settings) { myFeature.settings = settings; }, readSettings : function() { console.log(myFeature.settings); } }; myFeature.myProperty; // 'ciao' myFeature.myMethod(); // registra 'ciao' myFeature.init({ foo : 'bar' }); myFeature.readSettings(); // registra { foo : 'bar' }
Come è possibile applicare questo schema nel codice jQuery? Per esempio, nel codice seguente scritto nello stile tradizionale di jQuery:
// Facendo clic su un item della lista // si carica un certo contenuto. // Utilizzando l'ID dell'item e a sua // volta nasconde gli altri item fratelli. $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }); });
10.2: Utilizzare un oggetto letterale per una funzionalità jQuery
var myFeature = { init : function(settings) { myFeature.config = { $items : $('#myFeature li'), $container : $('<div class="container"></div>'), urlBase : '/foo.php?item=' }; // permete di sovrascrivere // la configurazione predefinita $.extend(myFeature.config, settings); myFeature.setup(); }, setup : function() { myFeature.config.$items .each(myFeature.createContainer) .click(myFeature.showItem); }, createContainer : function() { var $i = $(this), $c = myFeature.config.$container.clone() .appendTo($i); $i.data('container', $c); }, buildUrl : function() { return myFeature.config.urlBase + myFeature.$currentItem.attr('id'); }, showItem : function() { var myFeature.$currentItem = $(this); myFeature.getContent(myFeature.showContent); }, getContent : function(callback) { var url = myFeature.buildUrl(); myFeature.$currentItem .data('container').load(url, callback); }, showContent : function() { myFeature.$currentItem .data('container').show(); myFeature.hideContent(); }, hideContent : function() { myFeature.$currentItem.siblings() .each(function() { $(this).data('container').hide(); }); } }; $(document).ready(myFeature.init);
I vantaggi sono:
- Separazione di ogni funzionalità in piccoli metodi. In futuro, se si desidera cambiare la modo di visualizzare il contenuto, ci sarà chiaro dove farlo. Nel codice originale, questo passaggio è molto più difficile da individuare.
- Si rimuovono gli usi di funzioni anonime.
- Le opzioni di configurazione ssi sono spostate in una posizione centrale.
- Si eliminano le limitazioni che hanno catene di metodi, rendendo il codice più facile da rifattorare, mescolare e riordinare.
Grazie alle sue caratteristiche, l’uso di oggetti letterali consente un netto miglioramento per lunghi tratti di codice inseriti in un blocco $(document).ready(). Tuttavia, no sono più avanzati che avere diverse dichiarazioni di funzioni all’interno di un blocco $(document).ready().
10.3: Il modello modulare
var feature =(function() { // variabili e funzioni private var privateThing = 'segreto', publicThing = 'non segreto', changePrivateThing = function() { privateThing = 'super segreto'; }, sayPrivateThing = function() { console.log(privateThing); changePrivateThing(); }; // API pubblica return { publicThing : publicThing, sayPrivateThing : sayPrivateThing } })(); feature.publicThing; // registra 'non segreto' feature.sayPrivateThing(); // registra 'segreto' e cambia il valore // di privateThing a 'super segreto'
Il modello è potente perché permette di avere variabili e funzioni private, esponendo una API limitata che consente la retituzione di proprietà e metodi di un oggetto.
Di seguito è riportato una revisione dell’esempio visto prima, con le stesse caratteristiche, ma esponendo un unico metodo pubblico del modulo, showItemByIndex().
10.4: Utilizzare il modello modulare per una funzionalità jQuery
$(document).ready(function() { var feature = (function() { var $items = $('#myFeature li'), $container = $('<div class="container"></div>'), $currentItem, urlBase = '/foo.php?item=', createContainer = function() { var $i = $(this), $c = $container.clone().appendTo($i); $i.data('container', $c); }, buildUrl = function() { return urlBase + $currentItem.attr('id'); }, showItem = function() { var $currentItem = $(this); getContent(showContent); }, showItemByIndex = function(idx) { $.proxy(showItem, $items.get(idx)); }, getContent = function(callback) { $currentItem.data('container').load(buildUrl(), callback); }, showContent = function() { $currentItem.data('container').show(); hideContent(); }, hideContent = function() { $currentItem.siblings() .each(function() { $(this).data('container').hide(); }); }; $items .each(createContainer) .click(showItem); return { showItemByIndex : showItemByIndex }; })(); feature.showItemByIndex(0); });
Quando un progetto raggiunge una certa dimensione, cominicia a diventare difficile la gestione dei moduli in un’applicazione, giacché è necessario sapere ordinarli in modo corretto, e cominciare a combinarli in un unico file per ottenere il minor numero di richieste. È anche possibile che si voglia caricare codice “al volo” dopo il caricamento della pagina.
RequireJS è uno strumento per la gestione delle dipendenze creato da James Burke, che aiuta a gestire i moduli, caricarli nel giusto ordine e combinarli con facilità, senza dover apportare alcuna modifica. A sua volta, fornisce un modo semplice per caricare il codice una volta caricata la pagina, permettendo di minimizzare il tempo di download.
RequireJS possiede un sistema modulare, ma non c’è bisogno di seguirlo per ottenere i sui benefici. Il formato modulare di RequireJS permette la scrittura del codice incapsulato, incorporazione dell’internazionalizzazione (i18n) ai pacchetti (per consentirne l’uso in diverse lingue) e anche l’uso dei servizi JSONP come dipendenze.
Ottenere RequireJS
È anche utile scaricare un esempio di progetto jQuery che utilizza RequireJS.
10.5: Usare RequireJS: un semplice esempio
<!DOCTYPE html> <html> <head> <title>jQuery+RequireJS pagina di esempio</title> <script src="scripts/require-jquery.js"></script> <script>require(["app"]);</script> </head> <body> <h1>jQuery+RequireJS pagina di esempio</h1> </body> </html>
<script>require(["scripts/app.js"]);</script>
10.6: Un semplice file JavaScript con dipendenze
require(["jquery.alpha", "jquery.beta"], function() { // le estensioni jquery.alpha.js y jquery.beta.js // sono state caricate. $(function() { $('body').alpha().beta(); }); });
Se il modulo non ha dipendenze, solo si deve specificare il il nome come primo argomento di require.def(). Il secondo argomento è un oggetto letterale che definisce le proprietà del modulo. Per esempio:
10.7: Definizione di un modulo RequireJS che non ha dipendenze
require.def("my/simpleshirt", { color: "black", size: "unisize" } );
Se il modulo ha dipendenze, si può specificare nel secondo argomento di require.def() attraverso un array e poi passare una funzione come terzo argomento. Questa funzione sarà chiamata per definire il modulo una volta caricate tutte le dipendenze. Questa funzione riceve i valori restituiti dalle dipendenze come argomento (nello stesso ordine in cui sono dichiarate nell’array), allora la stessa deve restituire un oggetto che definisca il modulo.
10.8: Definizione di un modulo RequireJS con dipendenze
require.def("my/shirt", ["my/cart", "my/inventory"], function(cart, inventory) { // restituisce un oggetto che definisce // il modulo "my/shirt". return { color: "blue", size: "large" addToCart: function() { inventory.decrement(this); cart.add(this); } } } );
my/cart.js my/inventory.js my/shirt.js
I moduli non devono per forza restituire un oggetto, qualsiasi tipo di valore è permesso.
10.9: Definizione di un modulo RequireJS che restituisce una funzione
require.def("my/title", ["my/dependency1", "my/dependency2"], function(dep1, dep2) { // restituisce una funzione per definire "my/title". // Questo restituisce o imposta // il titolo della finestra return function(title) { return title ? (window.title = title) : window.title; } } );
requirejs/ (utilizzayo per eseguire gli strumenti) webapp/app.html webapp/scripts/app.js webapp/scripts/require-jquery.js webapp/scripts/jquery.alpha.js webapp/scripts/jquery.beta.js
10.10: File di configurazione per gli strumenti di ottimizzazione di RequireJS
{ appDir: "../", baseUrl: "scripts/", dir: "../../webapp-build", // Commentare la riga seguente se volete // minimizzare il codice dal compilatore // in modalità "semplice" optimize: "none", modules: [ { name: "app" } ] }
Per avviare l’elaborazione dei files, aprire una finestra di comando, andare alla cartella webapp/scripts ed eseguire:
# per sistemi non-Windows ../../requirejs/build/build.sh app.build.js # per sistemi Windows ..\..\requirejs\build\build.bat app.build.js
var myPortlet = Portlet({ title : 'Curry', source : 'data/html/curry.html', initialState : 'open' // or 'closed' }); myPortlet.$element.appendTo('body');
myPortlet.open(); // forza per aprire myPortlet.close(); // forza per chiudere myPortlet.toggle(); // alterna gli stati // tra aperto e chiuso myPortlet.refresh(); // aggiorna il contenuto myPortlet.destroy(); // rimuove il porlet dalla pagina myPortlet.setSource('data/html/onions.html'); // cambia il codice
Ancora nessun commento