jQuery vs MooTools

Maggio 2009 - Aaron Newton, Clientcide
Traduzione di Lorenzo Stanco e Stefano Ceschi Berrini

Sempre più spesso chi si avvicina alla programmazione in JavaScript si imbatte nella difficile scelta della libreria da adottare, o quantomeno della prima da imparare. Se lavori all'interno di un team probabilmente già ti è stato imposto un framework, con motivazioni più o meno discutibili. Se questo è il tuo caso, se sei costretto a usare MooTools ma sei pratico con jQuery, allora questo articolo può ugualmente esserti d'aiuto.

Ogni giorno su Twitter leggo innumerevoli post del tipo "MooTools o jQuery?". Questo articolo vuole aiutarti a scegliere.

Premessa

Sono uno sviluppatore MooTools. Lavoro sul framework MooTools. Scrivo un blog su MooTools. Ho scritto il principale tutorial online e il libro su MooTools. Ovviamente ho una visione che è in qualche modo di parte. E ammetto anche di non usare molto spesso jQuery. Se sei uno sviluppatore jQuery e pensi che abbia travisato qualcosa, ti prego di contattarmi e aiutarmi a rimediare l'errore. Il mio obiettivo è quello di esser d'aiuto - non quello di vendere un framework piuttosto che un altro.

Scopo

Aiutarti a scegliere tra questi due framework significa per me spiegartene le differenze. Comincio col dire che entrambi sono scelte eccellenti. Non esiste la scelta sbagliata. Entrambi i framework hanno i loro punti di forza e le loro debolezze, ma, in generale, sono entrambi ottime scelte. Ci sono anche ulteriori framework che fanno bene il loro lavoro. Dojo, Prototype, YUI, Ext e altri ancora sono ottime opzioni. La scelta, in verità, ha più a che fare con il tuo stile personale e con i tuoi bisogni. L'articolo si concentra su MooTools e jQuery, in quanto sono sempre più i framework presi in considerazione dalla maggior parte delle persone. Non cercherò di convincere nessuno a passare da un framework a un altro. Entrambi offrono spunti interessanti da cui è possibile imparare molto. Se vuoi saperne di più su questo articolo e del perché l'ho scritto puoi leggere il mio post sul blog di Clientcide.

Indice

Qualche dato

jQuery Core MooTools Core
Dimensione 55.9K 64.3K
Caratteristiche
Licenza MIT & GPL MIT
Strumenti per il DOM
Animazioni
Gestione degli eventi
Selettori CSS3 sì (un sottoinsieme) sì (un sottoinsieme)
Ajax
Estensioni ai tipi nativi (eccetto Element) circa una dozzina per Array, Object e String circa sei dozzine per Array, Object, String, Function e Number
Ereditarietà Non supportata direttamente da jQuery Fornita col costruttore Class
Altre considerazioni
Plugin Centinaia di plugin non ufficiali su plugins.jquery.com Circa 4 dozzine di plugin ufficiali disponibili su mootools.net/more. Plugin non ufficiali su mootools.net/plugins.
Libreria ufficiale per la UI no

Informazioni basate sui dati forniti da jquery.com, mootools.net e wikipedia.com.

Il Motto Dice Tutto

Se si va sul sito di jQuery, all'inizio della pagina c'è scritto:

jQuery è una libreria JavaScript veloce e concisa che semplifica la navigazione del documento HTML, la gestione degli eventi, le animazioni e le interazioni Ajax per un rapido sviluppo web. jQuery è progettato per cambiare il modo in cui si scrive JavaScript.

...mentre se si va sul sito di MooTools, ecco cosa si troverà scritto:

MooTools è un framework JavaScript Object-Oriented compatto e modulare, progettato per lo sviluppatore JavaScript medio/avanzato. Permette di scrivere codice potente, flessibile e cross-browser con le sue eleganti, ben documentate e coerenti API.

Penso che questo dica già tutto. Se vuoi sapere cosa ne penso io (e stai leggendo, quindi assumo che tu lo voglia), la domanda non è quale sia il framework migliore o peggiore. Quale delle cose scritte qui sopra vuoi fare? Questi due frameworks non stanno cercando di fare le stesse cose. Si sovrappongono un po' rispetto alle funzionalità che mettono a disposizione, ma non stanno cercando di fare le stesse cose.

La descrizione di jQuery stesso parla di HTML, eventi, animazioni, Ajax e sviluppo web. MooTools parla di orientamento agli oggetti (OOP) e riguarda lo scrivere codice potente e flessibile. jQuery aspira a "cambiare il modo di scrivere JavaScript" mentre MooTools è progettato per lo sviluppatore JavaScript medio/avanzato.

Parte di questa considerazione deriva dalla nozione di framework e toolkit. MooTools è un framework che cerca di implementare JavaScript come dovrebbe essere (in accordo con gli autori di MooTools). Lo scopo è quello di implementare una API che sembra JavaScript puro e che lo esalti; non riguarda solamente il DOM. jQuery è un toolkit che mette a disposizione un insieme di metodi facili da utilizzare in un sistema progettato per rendere il DOM stesso più piacevole. Accade spesso che il DOM sia l'area in cui la maggior parte delle persone scrive JavaScript, quindi in molti casi jQuery è tutto quello che serve.

Quando si scrive codice con MooTools si ha la sensazione di scrivere JavaScript puro. Se non si è interessati riguardo al JavaScript come linguaggio, imparare MooTools può sembrare un duro lavoro. Se si è invece interessati a JavaScript ed in particolare a cosa lo rende interessante, la potenza e l'espressività, beh, personalmente penso che MooTools sia la scelta migliore.

La Curva di Apprendimento e la Community

Per prima cosa, jQuery è sicuramente più facile da imparare. Ha uno stile quasi colloquiale che neanche ti sembrerà di programmare. Se tutto quello che interessa è ottenere qualcosa che funzioni velocemente, senza dover imparare JavaScript, jQuery è probabilmente la scelta migliore. Non che MooTools non aiuti ad ottenere gli stessi risultati, ma ammetto che può essere più ostico da imparare se se si è principianti in JavaScript. Inoltre ci sono moltissime risorse che aiutano ad imparare jQuery - almeno, più di quelle che ci sono per imparare MooTools.

Se si mette a confronto la community di jQuery (vedi la pagina "Discussion") e quella di MooTools (irc, mailing list, e forum non ufficiale) si notano subito due cose: 1) la community di jQuery è molto più grande (personalmente attribuisco questo fatto a quanto detto prima sulla facilità di apprendimento, ma non solo...) e 2) sono molto più attivi nel promuovere la libreria. Misurando jQuery e MooTools sul numero degli utenti, sul numero delle ricerche su Google, sul numero di libri venduti, ecc, jQuery stravince.

Detto questo, per spiegare perché si dovrebbe prendere in considerazione anche MooTools devo prima mostrare cosa fanno i due framework (e come lo fanno). Alla fine la scelta sarà dettata da cosa si vuol realizzare e da come si preferisce programmare (e forse anche dal se piace programmare, almeno in JavaScript).

I Punti di Forza di JavaScript

Parte della decisione sta nel domandarsi che cosa si vuole fare con JavaScript. Consideriamo il JavaScript base. Senza nessun framework; solamente vecchio e puro JS. Mette a disposizione oggetti nativi come Strings, Numbers, Functions, Arrays, Dates, Espressioni regolari, e più. Inoltre predispone un modello di ereditarietà - un qualche esoterico modello chiamato ereditarietà prototipale (ne parlerò più avanti). Questi blocchi portanti ed il concetto di ereditarietà sono pane e burro di ogni linguaggio di programmazione e non hanno assolutamente niente a che vedere con i browser o con il web o con CSS o HTML. Si potrebbe scrivere qualsiasi cosa si voglia con JavaScript. Tris, scacchi, photo editing, un server web, qualunque cosa. Il fatto è che il 99% di tutto il JS che c'è in giro lo vediamo eseguito sui browser ed è in questo contesto che lo pensiamo. Ossia il linguaggio di programmazione per il browser.

Sapere che il browser, il DOM, è l'area dove la maggior parte delle volte utilizziamo JavaScript ma che quest'ultimo è anche un linguaggio di programmazione molto robusto ed espressivo aiuterà a capire le differenze tra MooTools e jQuery.

Non Solo il DOM

Se si pensa a cosa si vuole fare con JavaScript in termini di "prendere elementi nella pagina per farne qualcosa" allora jQuery è probabilmente la scelta migliore. Eccelle nell'offrire un sistema molto espressivo per descrivere il comportamento nelle pagine in un modo che quasi non sembra programmazione vera e propria. Si può sempre continuare ad usare il resto in JavaScript per fare quello che si vuole, ma se si è focalizzati sul DOM - per cambiare proprietà CSS, animare elementi, recuperare dei contenuti via Ajax, etc - tutto quello che si vorrà fare sarà ottenibile tramite jQuery, più qualcosa - se mancante in jQuery - in vecchio e puro JavaScript. jQuery mette anche a disposizione metodi che non riguardano prettamente il DOM; ad esempio, offre un meccanismo per iterare sugli array - $.each(array,fn) - oppure, ad esempio, offre un metodo per il trim delle stringhe - $.trim(str). Ma non sono presenti molti metodi di questo tipo, che è un bene, perché, per la maggior parte, se si stanno solo prendendo elementi dal DOM, ci si itera sopra, e li si altera (aggiungendo HTML, cambiando stili, aggiungendo dei listener per eventi quali click e mouseover, etc) non serve molto altro.

Ma se si pensa al JavaScript nella sua totalità, si può notare come jQuery non si focalizzi in cose al di fuori del DOM. Questa è una delle ragioni per cui è così facile da imparare, ma altresì limita il modo in cui può aiutare a scrivere JavaScript. Vuol essere niente più che un solido sistema di programmazione per il DOM. Non prevede ereditarietà e nemmeno dispone di utilità per tutti i tipi nativi del linguaggio JavaScript, ma non ne ha la necessità. Se si vogliono fare magie con stringhe, date, espressioni regolari, array e funzioni, è possibile. Ma aiutare in questo non è compito di jQuery. JavaScript come linguaggio è quello che serve. jQuery rende il DOM il tuo ambiente, ma il resto del linguaggio JavaScript è fuori dalla sua ottica.

È qui la differenza maggiore rispetto a MooTools. Piuttosto che focalizzarsi esclusivamente sul DOM (comunque, e ci tornerò piu avanti, offre tutte le stesse funzionalità di jQuery ma consegue lo scopo in maniera totalmente differente), MooTools prende sotto suo dominio tutto l'intero linguaggio. Se jQuery rende il DOM l'ambiente operativo, MooTools mira a rendere tutto JavaScript come ambiente operativo, e questa è una delle ragioni per cui è più ostico da imparare.

L'Ereditarietà in JavaScript

Il linguaggio di programmazione JavaScript offre alcune cose veramente fantastiche a riguardo. Per i principianti, è un linguaggio funzionale, significa che tratta le funzioni come oggetti di ordine superiore che possono essere passati come variabili praticamente come tutti gli altri oggetti - stringhe o numeri ad esempio. È progettato su questo concetto base e molti dei metodi e dei pattern presenti funzionano al meglio quando il codice viene scritto in questo modo. È la differenza tra:

for (var i = 0; i < myArray.length; i++) { /* fai qualcosa */ }

e:

myArray.forEach(function(item, index) { /* fai qualcosa */ });

JavaScript ha un modello di ereditarietà che non è unico ma piuttosto raro nei linguaggi di programmazione. Invece dell'ereditarietà a classi (una classe può essere sottoclasse etc) JS vira verso l'ereditarietà prototipale. Questo significa che gli oggetti ereditano direttamente da altri oggetti. Se si referenzia una proprietà in un oggetto che eredita da un altro oggetto, il linguaggio ispeziona l'oggetto figlio per quella proprietà e, se non la trova, la cerca nel padre. Questo è il modo in cui funziona sugli array. Quando si scrive:

[1,2,3].forEach(function(item) { alert(item) }); //alert di 1 poi 2 ed infine 3

il metodo "forEach" non è una proprietà dell'array che viene dichiarato ([1,2,3]), ma è una proprietà del prototipo per tutti gli Array. Quando si referenzia questo metodo il linguaggio cerca forEach nell'array, e, non trovandolo, guarda all'interno del prototipo 'padre' di tutti gli array. Questo significa che il metodo forEach non è in memoria per ogni array dichiarato; è in memoria soltanto per il prototipo degli array. In due parole: efficienza e potenza. (nota: l'alias utilizzato in MooTools per forEach è each)

Autoreferenza

In JavaScript esiste una keyword speciale: "this". È difficile per me in poche parole definirla ma, di default, "this" è l'oggetto al quale il metodo corrente appartiene. Permette agli oggetti di riferirsi a se stessi all'interno dei loro metodi, in quanto altrimenti non avrebbero altri mezzi per farlo. Tutto questo inizia a diventare importante quando si creano oggetti figli e numerose istanze di quell'oggetto; come altro potrebbe il metodo di un oggetto riferirsi all'oggetto stesso? Quando la copia attuale del metodo esiste nel padre, non nel figlio, la keyword "this" permette a queste istanze di riferirsi al loro stato. (qui c'è una piu completa descrizione a riguardo, ed un'altra da Mozilla)

La keyword "this" permette agli oggetti che ereditano da altri oggetti di riferirsi a loro stessi, ma ci sono volte in cui si vorrebbe far riferimento a qualcos'altro attraverso il "this". Questo procedimento è chiamato binding, ossia quando si specifica un "this" diverso per un metodo. Il metodo "each" (del tipo Array) permette di specificare l'oggetto a cui far riferimento attraverso un secondo argomento. Qui sotto c'è un esempio che esplica il passaggio di un "this" differente:

var ninja = {
    weapons: ['katana', 'throwing stars', 'exploding palm technique'],
    log: function(message) {
        console.log(message);
    },
    logInventory: function() {
        this.weapons.each(function(weapon) {
            //vogliamo che "this" faccia riferimento a ninja...
            this.log('this ninja can kill with its ' + weapon);
        }, this); //quindi passiamo "this" (che è ninja) ad Array.each
    }
};
ninja.logInventory(); 
//this ninja can kill with its katana
//this ninja can kill with its throwing stars
//this ninja can kill with its exploding palm technique

Nell'esempio qui sopra, "leghiamo" ninja (che è il "this" dentro al metodo logInventory) al metodo che passiamo all'array in modo che ci si possa riferire alla proprietà log di ninja. Se non l'avessimo fatto, "this" sarebbe stato window.

Questi sono solamente alcuni esempi del potere e dell'espressività che JavaScript ha da offrire - ereditarietà, auto referenza e binding, ed efficienti proprietà prototipali. La brutta notizia riguarda il fatto che JavaScript puro non rende queste cose potenti molto usabili o accessibili, ed è qui che entra in gioco MooTools. Rende questi tipi di pattern semplici ed anche piacevoli da usare. Si finisce per usare codice piu astratto, ed a lungo andare, è una buona cosa - una cosa potente. Capire come questi patterns siano preziosi e come usarli correttamente richiede uno sforzo, ma la cosa positiva è che il codice che si scrive è molto più riusabile e facile da mantenere. Andrò nei dettagli a riguardo tra un minuto.

MooTools Rende JavaScipt Più Divertente

Dato che MooTools si focalizza nel rendere le API JavaScript stesse piu stabili e coerenti, perde un po' nel dare un'interfaccia che "cambia il modo in cui si scrive JavaScript" e nel rendere JavaScript nell'insieme meno frustrante; MooTools è un'estensione del linguaggio JavaScript. MooTools prova a rendere il JavaScript così com'è stato pensato. Una parte significativa del "core" viene utilizzata per arricchire Funzioni, Stringhe, Array, Number, Element ed altri prototipi. L'altro enorme aspetto del "core" di MooTools è che offre una funzione chiamata Class.

Ora, attraverso Class sembrerebbe si stesse cercando di ricreare un più classico modello di ereditarietà che si puo trovare in Java o C++, ma non è così. Quello che fa Class è rendere il modello di ereditarietà prototipale del JavaScript più facile ed accessibile e qualcosa da cui prendere vantaggio. Da notare che questi concetti non sono unici in MooTools (altri frameworks offrono funzionalità similari), ma non sono presenti in jQuery. Quest'ultimo non offre un sistema di ereditarietà né offre estensioni per gli oggetti nativi (Function, String, etc). Sia chiaro, non è un difetto di jQuery, in quanto i suoi autori potrebbero rendere facilmente disponibili queste cose. Loro hanno invece progettato un toolkit con un obiettivo diverso in mente. Mentre MooTools mira a rendere JavaScript più divertente, jQuery mira a rendere il DOM più divertente ed i progettisti hanno scelto di limitare i loro scopi a questi compiti.

jQuery Rende il DOM Più Divertente

Ed è per questo che jQuery è più accessibile. Non presuppone che si impari JavaScript in lungo ed in largo. Non mette di fronte l'ereditarietà prototipale, binding, "this" e prototipi nativi. Quando si inizia a scrivere codice con jQuery tramite il tutorial ufficiale, questo è il primo esempio che ci si trova davanti:

window.onload = function() {
    alert("benvenuto");
}

e qui c'è il terzo:

$(document).ready(function() {
    $("a").click(function(event) {
        alert("Grazie della visita!");
    });
});

Se si legge il libro su MooTools oppure il tutorial di MooTools (dei quali ne sono l'autore) questi approcciano in maniera completamente differente. Mentre si può saltare gran parte ed andare direttamente ad imparare cose su effetti e DOM, se si vuol imparare seriamene MooTools, bisognerebbe iniziare con cose come Class, e, lo ammetto, se si è alle prime armi con la programmazione, o si vuol solamente qualcosa che funzioni nel proprio sito senza approfondire il linguaggio JavaScript, jQuery sicuramente risulterà molto più semplice ed "amichevole".

D'altra parte, se si vuol veramente imparare JavaScript, MooTools è un gran bel mezzo per riuscirci. Implementa molte cose che JavaScript deve ancora avere (molti dei metodi che estendono i tipi nativi sono solo le specifiche js 1.8 ed oltre). Se si è abituati a programmare, specialmente sia programmazione orientata agli oggetti che funzionale, MooTools offre tantissimi design patterns interessanti ed espressivi.

Qualunque Cosa Tu Faccia, Io Posso Farla Meglio

Se si guarda a quello che jQuery può fare, c'è sempre una stessa funzionalità in MooTools. Riguardo quello che MooTools può fare, non c'è modo a volte di emularle in codice jQuery dato che quest'ultimo si focalizza solamente sul DOM. MooTools ha più funzionalità di jQuery, ma non c'è niente in jQuery che permetta di emularle. Ad esempio, jQuery non fornisce nessun sistema di ereditarietà, ma non c'è problema. Si potrebbe, se si vuole, utilizzare il modulo Class di MooTools in aggiunta a jQuery. C'è inoltre un plugin che mette a disposizione l'ereditarietà in jQuery (non l'ho utilizzato, ma penso offra più o meno lo stesso tipo di funzionalità)

Se vediamo questo pezzo di codice jQuery dall'esempio sopra:

$(document).ready(function() {
    $("a").click(function(event) {
        alert("Grazie per la visita!");
    });
});

e volessimo 'tradurlo' in MooTools, avremmo:

window.addEvent('domready', function() {
    $$('a').addEvent('click', function(event) {
        alert('Grazie per la visita!');
    });
});

Sono molto simili no?

Prendiamo ora un esempio un po' più complesso in jQuery:

$(document).ready(function() {
    $("#orderedlist li:last").hover(function() {
        $(this).addClass("green");
    },
    function() {
        $(this).removeClass("green");
    });
});

ed in MooTools diverrà:

window.addEvent('domready',function() {
    $$('#orderedlist li:last-child').addEvents({
        mouseenter: function() {
            this.addClass('green');
        },
        mouseleave: function() {
            this.removeClass('green');
        }
    });
});

Ancora, molto simili. Direi che la versione MooTools è più esplicita, ma anche molto verbosa. È chiaro che leggendo il codice MooTools si intuisca che stiamo aggiungendo due eventi - uno per l'entrata del mouse e l'altro per l'uscita del mouse, mentre la versione jQuery è più concisa; il suo metodo hover accetta due metodi - il primo per l'entrata del cursore del mouse ed il secondo per l'uscita del cursore del mouse. Io personalmente preferisco il codice MooTools in quanto è più leggibile, ma è un'osservazione prettamente personale.

Devo dire inoltre che qualche volta jQuery può diventare troppo esoterico per i miei gusti. I metodi non sempre hanno senso per me ed a prima vista li trovo difficili da analizzare. Comunque, anche se non sarebbe molto giusto da dire dato che sono in intimità con MooTools, per me leggere MooTools è più facile. Una delle cose che apprezzo di MooTools riguarda il fatto che praticamente tutti i nomi dei metodi e delle classi danno il vero nome alle cose. I metodi sono quasi sempre verbi e lasciano pochi dubbi riguardo quello che fanno. Ogni linguaggio di programmazione richiede una ricerca nella documentazione per la sintassi quando si scrive il codice - Non parlo di questo. Sto dicendo solamente che trovo le API di MooTools più coerenti e consistenti.

MooTools Fa Ciò Che Vuoi Come Vuoi

E se ti piace la sintassi jQuery? Un modo per illustrare il potere di MooTools è mostrare quanto facile sia cambiare il codice in modo che si addica ai propri gusti. Se avessimo voluto implementare il metodo hover da jQuery a MooTools, avremmo potuto facilmente fare così:

Element.implement({
    hover : function(enter,leave){
       return this.addEvents({ mouseenter : enter, mouseleave : leave });
    }
});

//e quindi lo si potrà usare esattamente come nella versione jQuery:
$$('#orderlist li:last').hover(function(){
   this.addClass('green');
},
function(){
   this.removeClass('green');
});

Ci sono addirittura plugins MooTools che fanno questo; forniscono una sintassi jQuery per MooTools. Il focalizzarsi di MooTools riguardo l'estensibilità significa che si può implementare praticamente qualsiasi cosa si voglia. Questo è qualcosa che jQuery non può fare. MooTools può simulare jQuery volendo, ma jQuery non può mimare MooTools. Se si vogliono scrivere classi oppure estendere i prototipi nativi o fare qualcosa che MooTools può fare, avendo adottato jQuery bisognerà farlo a mano.

La Concatenazione Come Design Pattern

Facciamo un altro di questi esperimenti. Qui sotto c'è del codice jQuery (dal tutorial ufficiale):

$(document).ready(function() {
    $('#faq').find('dd').hide().end().find('dt').click(function() {
        $(this).next().slideToggle();
    });
});

Questo è un esempio di sintassi che personalmente non mi alletta. Guardando il codice qui sopra, non sono molto sicuro di quello che stia facendo. In particolare sarei curioso di capire cosa fa .end e come .find, che lo segue, sia in relazione a quello che fa .end. Ora, guardando la documentazione jQuery si capisce benissimo cosa fa .end (resetta il valore del selettore originale, in questo caso #faq). Ma a me sembra strano. Quando lavoro con jQuery, spesso mi ritrovo ad essere insicuro riguardo cosa faccia un particolare metodo. Ovviamente ciò riguarda solo me dato che jQuery viene utilizzato da tantissime persone (che sono contente di usarlo), quindi anche questa è una preferenza personale.

Vediamo la logica spiegata sopra in MooTools:

window.addEvent('domready', function() {
    var faq = $('faq');
    faq.getElements('dd').hide();
    faq.getElements('dt').addEvent('click', function() {
        this.getNext().slide('toggle');
    });
});

Ancora, il codice scritto con MooTools è più verboso, ma anche più esplicito. Si nota pure che il design pattern in questo caso riguarda salvare l'elemento #faq in una variabile (dove jQuery usa il metodo .end per ritornarla). Faccio notare che è possibile anche in MooTools scrivere codice concatenato. Ad Esempio:

item.getElements('input[type=checkbox]')
	.filter(function(box) {
		return box.checked != checked;
	})
	.set('checked', checked)
	.getParent()[(checked) ? 'addClass' : 'removeClass']('checked')
	.fireEvent((checked) ? 'check' : 'uncheck');

Comunque, scrivere codice come questo - con molta logica dentro domready - con entrambi i frameworks, direi che è una brutta abitudine. È' molto meglio incapsulare la logica in moduli riusabili.

Riutilizzo del codice con jQuery

È molto allettante quando si lavora ad un progetto web scrivere del codice in questo modo. Basta soltanto aggiungere un po' di logica nella pagina per selezionare gli elementi del DOM e "settarli" nascondendone alcuni, alterandone degli altri, ed aggiungendo degli event listeners per i click ed i mouseover. Sviluppare il codice in questo modo è molto efficiente, molto veloce. Il problema che nasce scrivendo tutta la propria logica all'interno della funzione associata alla domready riguarda il fatto che molti pezzi di codice fanno le stesse cose ma in posti differenti. Se prendiamo il pattern FAQ descritto sopra potremmo applicare la stessa logica in qualche altra parte in una pagina differente con qualsiasi lista di termini e definizioni. Si dovrà ripetere la stessa logica ogni volta che si troverà questo pattern?

Un metodo semplice per rendere il codice riusabile si ottiene "impacchettando" la logica in una funzione e passandogli degli argomenti. Qui di seguito ecco come si potrebbe fare in jQuery:

function faq(container, terms, definitions) {
    $(container).find(terms).hide().end().find(definitions).click(function() {
        $(this).next().slideToggle();
    });
};
$(document).ready(function() {
    faq('#faq', 'dd', 'dt');
});

Questo metodo è migliore per due ragioni importantissime:

  1. Se in futuro ci sarà bisogno di cambiare il modo in cui queste liste funzionano (ad esempio se si volesse tener traccia dei click in un log web oppure se si volessero recuperare le definizioni tramite ajax) potremmo solamente cambiare il nostro metodo faq invece che andare a modificare svariate copie ovunque. Di questo ne tengo una piccola traccia nella mia applicazione. Tenendo appunto traccia dei punti dove la mia applicazione tocca il codice generico, sarà più facile sistemare i bugs, aggiornare i frameworks, aggiungere features, o alterare funzionalità.
  2. La seconda: c'è meno codice. Riusando lo stesso metodo più volte, non bisognerà ripetersi e questo è un valore aggiunto in ogni ambiente di programmazione. Rende inoltre il codice che i visitatori devono scaricare meno pesante.

jQuery attualmente ha un metodo un po' più rifinito per scrivere "widget" riusabili come questi. Piuttosto che incoraggiare a mettere tutto dentro funzioni come nell'esempio qui sopra (che è piuttosto rudimentale), incoraggia a scrivere plugins jQuery. In questo modo:

jQuery.fn.faq = function(options) {
    var settings = jQuery.extend({
        terms: 'dt',
        definitions: 'dd'
    }, options); 
	  //"this" è il contesto corrente; in questo caso, gli elementi ai quali vogliamo applicare un layout faq
    $(this).find(settings.terms).hide().end().find(settings.definitions).click(function() {
        $(this).next().slideToggle();
    });
    return this;
};

che di conseguenza si potrebbe usare così:

$('#faq').faq();

Guardando all'esempio qui sopra, non c'è molta differenza tra dichiarare la nostra funzione faq in questo modo e dichiararla come una funzione vera e propria. Non è nel namespace globale, ma potremmo aggiungerlo facilmente. Rendendolo plugin jQuery potremmo quindi concatenarlo ad altri suoi metodi. L'altro beneficio riguarda il "this" che all'interno della nostra funzione è il contesto corrente dell'elemento nella concatenazione jQuery in quel momento. Utilizzando questo pattern per i plugins si potrà essere in grado di renderli parte integrante di jQuery, ma a parte quello, un plugin è praticamente una singola funzione che prende il contesto corrente jQuery, ci fa qualcosa, e poi ritorna il contesto per il prossimo elemento nella catena. Non è molto complesso il discorso, ed infatti questo rende molto semplice per chiunque scrivere plugins jQuery - sono solamente delle singole funzioni.

Si noti che è possibile scrivere plugins jQuery più complessi con i metodi e gli stati. Questo tipo di pattern è supportato dal sistema di plugins jQuery UI e non usa lo stesso meccanismo del plugin di base (come il nostro esempio delle faq). Invece, si attacca un oggetto con metodi e proprietà all'oggetto jQuery (i.e. $.ui.tabs). C'è un modo semplice per invocare questo oggetto ($(selector).tabs()) cosicché si possa continuare a concatenare come nel plugin faq. Dato che non ritorna una referenza all'oggetto tabs creato per gli elementi nel nostro selettore, si è forzati a chiamare ancora il selettore per invocarci metodi. Invece di chiamare myTabInstance.add(url, label, index) bisognerà eseguire un'altra volta il selettore e chiamare la funzione (come stringa): $(selector).tabs('add',url,label, index). Questo significa che si stanno valutando i selettori 2 volte (a meno che non si salvino in una variabile da qualche parte), e che non si ha mai un puntatore al metodo "add" che renda possibile un bind oppure un delay. Questo post comunque è focalizzato sui core di MooTools e jQuery, e mentre il sistema jQuery UI fornisce questa funzionalità, non è qualcosa che è disponibile in jQuery di default.

Riutilizzo del Codice con MooTools

In MooTools quando si vuol definire un pattern, si tende o ad usare Class oppure si implementa un metodo in un oggetto nativo (su String, ad esempio).

Piuttosto che dare un linguaggio completamente differente dallo stile nativo di JavaScript, MooTools cerca di stare in mezzo alla definizione di una sintassi personalizzata e l'estensione dei design patterns propri di JavaScript. Uno dei modi in cui lo fa è tramite l'estensione dei prototipi degli oggetti nativi nel linguaggio e nel DOM. Questo significa che se ci fosse bisogno di un metodo che fa il trim di una stringa, MooTools incoraggia ad aggiungere quel metodo nell'oggetto String (notare che String.trim è già presente in MooTools; non serve aggiungerlo a mano):

String.implement({
    trim: function() {
        return this.replace(/^\s+|\s+$/g, '');
    }
});

Questo significa che si può solamente eseguire " non più spazi alla fine! ".trim() ed ottenere "non più spazi alla fine!". Alcuni direbbero che implementare proprietà direttamente nei prototipi nativi è inappropriato. È questa la ragione per la quale MooTools e Prototype.js non possono essere usati assieme - i framework che manipolano i prototipi dei tipi nativi non possono essere usati con altri frameworks che fanno lo stesso. Se si definisce String.prototype.foo() ed un'altra libreria nella stessa pagina definisce lo stesso, 'vince' il metodo che viene definito per ultimo. In un certo modo, si può dire che è lo stesso problema che si incontra con il namespace globale window. Comunque è così che funziona JavaScript. È in questo modo che JavaScript 1.8 ha aggiunto moltissime features. Le aggiunge direttamente ai prototipi.

Gli sviluppatori MooTools hanno lavorato in modo che il framework sia semplice da estendere e da utilizzare nella sua totalità, senza ricorrere ad altri frameworks. Inoltre sarebbe scortese domandare agli utenti di scaricare due frameworks diversi. La sola ragione valida per includere due frameworks riguarda il voler usufruire di plugins per entrambi, e nella mente degli autori di MooTools (io compreso), se si vuole un plugin che non è disponibile per il framework adottato, sarebbe più appropriato spendere del tempo per fare il porting per il proprio ambiente piuttosto che far scaricare agli utenti un altro framework.

Una volta che si è capito come funziona JavaScript e si è realizzato il potere dell'estensione dei tipi nativi, si scoprirà un nuovo metodo di programmazione. Si potranno scrivere plugins che alterano Elements, oppure Dates, o Functions. Mentre qualcuno direbbe che aggiungere metodi ai tipi nativi è "sporco", io dico che è proprio questa la feature principale di JavaScript. È una sua caratteristica architetturale. Aggiungendo metodi ai tipi nativi il codice risulterà conciso e compartimentalizzato. Anche jQuery utilizza questo metodo, ma limita il prototyping al solo oggetto jQuery (alias $).

Mentre si possono concatenare chiamate a metodi sull'oggetto jQuery, negli altri tipi di oggetti non si potrà fare una concatenazione. Ad esempio, se si vuole in jQuery fare il trim di una stringa e poi iterare ogni riga, si dovrebbe scrivere:

$.each( $.trim( $('span.something').html() ).split("\n"), function(i, line){alert(line);});

In MooTools invece, dato che si modificano i prototipi, si può fare questo:

$('span.something').get('html').trim().split("\n").each(function(line){alert(line);});

Solamente guardando a questo esempio risulta estremamente chiara la potenza dell'estensione dei prototipi. Il concatenamento negli elementi del DOM non è il solo posto dove risulta utile. MooTools fa si che si possano concatenare chiamate a metodi su tutti gli oggetti, incluse chiamate ad un metodo su più elementi in una singola volta.

La chiave qui, ed è il cuore del framework MooTools, è il guidare a programmare ciò che si vuole. Se una funzionalità non è presente nel core, lo si può estendere aggiungendola. L'obiettivo del core non è provvedere ad ogni singola funzionalità possibile ed immaginabile, ma è mettere a disposizione gli strumenti per scrivere ciò che si vuole. Una grossa parte riguarda la facilità con la quale si possono estendere i tipi nativi, traendo vantaggio dall'ereditarietà prototipale. Si possono fare tutte queste cose in JavaScript nativo, ma MooTools rende il tutto più semplice e più piacevole.

L'Ereditarietà in MooTools

Nonostante il suo nome, in MooTools la funzione Class non è veramente una classe e non crea classi. Ha uno schema di progettazione che potrebbe ricordare le classi di un linguaggio di programmazione tradizionale, ma in realtà Class riguarda sempre oggetti ed ereditarietà prototipale. (Sfortunatamente la parola "class" è quella che meglio descrive queste cose, quindi nell'articolo quando parlo di "classi" faccio riferimento a funzioni che ritornano oggetti - che chiamerò "istanze" - che ereditano da un prototipo.)

Per creare una classe, si deve passare un oggetto al costruttore Class in questo modo:

//classe per gli essere umani
var Human = new Class({
    initialize: function(name, age) {
        this.name = name;
        this.age = age;
    },
    isAlive: true,
    energy: 1,
    eat: function() {
        this.energy = this.energy + 1; //oppure this.energy++
    }
});

Passando l'oggetto a Class (nell'esempio abbiamo passato un oggetto con proprietà e metodi come "isAlive" e "eat") l'oggetto diventa il prototipo di ogni istanza di quella classe. Dopo aver creato la classe, è possibile crearne le istanze, ad esempio:

var bob = new Human("bob", 20); //bob si chiama "bob" e ha 20 anni.

Ora abbiamo una istanza di Human. bob ha le proprietà dell'oggetto definito al momento della creazione della class Human. Ma la cosa importante è che bob ha queste proprietà per ereditarietà. Quando facciamo riferimento a bob.eat, bob non ha davvero quel metodo. JavaScript sa che bob non ha un metodo eat, e quindi lo cerca lungo la catena di ereditarietà, trovandolo sull'oggetto che abbiamo passato quando abbiamo creato la classe Human. Questo vale anche per energy. A prima vista ciò può sembrare sbagliato; non vogliamo che tutti gli umani acquisiscano energia ogni volta che bob mangia. La cosa importante da capire è che appena assegniamo un valore all'energia di bob, assegniamo a bob un valore proprio, e non guardiamo più al valore del prototipo. Perciò la prima volta che bob mangerà, acquisirà una sua definizione di energy (pari a 2).

bob.eat(); //bob.energy == 2

Da notare che il nome e l'età di bob sono unicamente suoi; infatti queste due proprietà vengono ad esso assegnate durante l'inizializzazione della classe, nel metodo initialize.

Tutto questo comportamento può sembrare un po' strano, ma il suo punto di forza sta nel fatto che possiamo definire funzionalità per un certo modello e creare istanze di quel modello ogni volta che ne abbiamo bisogno. Ogni istanza manterrà il proprio stato. Dunque se creiamo un'altra istanza, ciascuna sarà indipendente dalle altre, ma erediterà dallo stesso modello base:

var Alice = new Human();
//alice.energy == 1
//bob.energy == 2

Le cose si fanno veramente interessanti quando cominciamo a sfruttare questo meccanismo aggiungendo dell'altro.

Estendere ed Implementare le Classi

Proviamo a rivisitare il plugin jQuery per le faq. Cosa accadrebbe se volessimo aggiungere un'ulteriore funzionalità al plugin? Se volessimo, ad esempio, fare una versione AJAX del plugin che legga le risposte alle domande dal server? Supponiamo che il plugin faq sia stato scritto da qualcun altro e che vogliamo arricchirlo senza modificare in alcun modo l'originale.

Abbiamo solo due possibili scelte. O duplichiamo l'intera logica del plugin faq (ricordo, è una singola funzione), praticamente realizzando un "fork", oppure lo invochiamo e aggiungiamo successivamente le nuove funzionalità ad esso. Dovendo scegliere, la seconda alternativa pare quella meno faticosa. Il plugin diventerebbe allora qualcosa come:

jQuery.fn.ajaxFaq = function(options) {
    var settings = jQuery.extend({ 
		//alcune opzioni specifiche per ajax, come l'url per la richiesta dei termini
        url: '/getfaq.php'
        definitions: 'dd'
    }, options); 
	//"this" è il contesto corrente; in questo caso, gli elementi ai quali vogliamo applicare un layout faq
    $(this).find(settings.definitions).click(function() {
        $(this).load(.....); //la logica per caricare il contenuto dei termini
    });
    this.faq(); //chiamata al plugin originale faq
});

Questo codice ha alcuni lati negativi. Prima di tutto, la nostra classe faq ripete la selezione delle definizioni, cosa che potrebbe anche essere pesante; non c'è alcun modo di memorizzare le definizioni recuperate e riutilizzarle quando servono nuovamente. In secondo luogo, non possiamo aggiungere il codice per la gestione di ajax in mezzo a quello per la visualizzazione delle definizioni, presente nel plugin originale. Il plugin originale chiama slideToggle per espandere le definizioni utilizzando una animazione. Questo è un problema in quanto l'effetto sicuramente partirà prima che la nostra richiesta ajax finisca di caricare. Non c'è nessuna soluzione definitiva se non quella di duplicare l'intero plugin faq.

Ora consideriamo la nostra classe Human realizzata con MooTools. Ha le proprietà isAlive e energy e ha un metodo chiamato eat. Se volessimo creare una nuova versione di Human con proprietà aggiuntive? Con MooTools possiamo estendere la classe in questo modo:

var Ninja = new Class({
    Extends: Human,
    initialize: function(name, age, side) {
        this.side = side;
        this.parent(name, age);
    },
    energy: 100,
    attack: function(target) {
        this.energy = this.energy - 5;
        target.isAlive = false;
    }
});

Si può vedere come abbiamo aggiunto molte funzionalità in una sottoclasse. Questa sottoclasse ha alcune proprietà che sono unicamente presenti nei Ninja. I Ninja hanno una energia (energy) iniziale pari a 100, un orientamento (side) e un metodo attack che gli permette di uccidere gli altri Humans a costo di alcuni punti energy.

var bob = new Human('Bob', 25);
var blackNinja = new Ninja('Nin Tendo', 'unknown', 'evil');
//blackNinja.isAlive = true
//blackNinja.name = 'Nin Tendo'
blackNinja.attack(bob);
//bob non ha chance

Uccisioni a parte, ci sono alcune cose interessanti da considerare. Il metodo initialize nella classe Ninja sembra ridefinire, sovrascrivendo, il metodo initialize della classe Human, ma è ancora possibile accedere ad esso chiamando this.parent, passando quindi gli argomenti che il metodo initialize della classe padre si aspetta. Inoltre possiamo decidere quando il nostro nuovo codice viene eseguito; prima o dopo la chiamata al padre. Possiamo assegnare nuovi valori alle proprietà (come energy, pari a 100 anziché 1), e definire nuove funzionalità. Immagina se avessimo potuto fare questo nel nostro plugin jQuery faq. Avremmo potuto caricare il nostro ajax e POI lanciare l'animazione di comparsa del contenuto.

MooTools ha un altro pattern chiamato Mixin. Invece di avere una relazione padre-figlio definita creando una sottoclasse per estensione di un'altra classe, è possibile anche definire una classe che viene miscelata ad altre classi affinché possa mettere a disposizione di queste le sue proprietà. Ecco un esempio:

//class guerriero (mixin)
var Warrior = new Class({
    energy: 100,
    kills: 0,
    attack: function(target) {
        target.isAlive = false;
        this.energy = this.energy - 5;
        this.kills++;
    }
});

Abbiamo preso le qualità che rendono un Ninja diverso da un Human e le abbiamo messe in una classe a parte. Questo ci permette di usare il codice non solo per i Ninja. Possiamo infatti mettere a disposizione dei Ninja le qualità di Warrior (guerriero) in questo modo:

var Ninja = new Class({
    Extends: Human,
    Implements: Warrior, //può essere un array se vogliamo implementare più di una classe
    initialize: function(name, age, side) {
        this.side = side;
        this.parent(name, age);
    }
});

Ninja funziona ancora come prima, ma Warrior è a nostra disposizione per essere riusato:

var Samurai = new Class({
  Extends: Human,
  Implements: Warrior,
  side: 'good' //orientamento "buono"
});

Ora abbiamo una classe Samurai e una classe Ninja. Si osservi quanto poco codice è stato necessario per definirle. Sono simili tra loro, perché entrambi umani con abilità da guerriero, ma sono differenti in quanto un samurai sarà sempre buono ("good"), mentre un ninja può cambiare il suo orientamento. Spendendo un po' di tempo per scrivere una classe Human e una classe Warrior, siamo in grado di avere tre diverse classi senza nessuna ripetizione di codice, mantenendo al tempo stesso un buon controllo sull'esecuzione dei metodi e sulla loro relazione con gli altri. Ogni istanza che creiamo ha un proprio stato, e il codice in sé è molto leggibile.

Ora che abbiamo visto come funzionano le classi in MooTools, torniamo alla nostra classe jQuery faq e proviamo a riscriverla in MooTools, aggiungendo poi la parte Ajax come abbiamo fatto con jQuery.

var FAQ = new Class({
	//Options è un'altra classe, disponibile con MooTools
	Implements: Options,
	//queste sono le opzioni di default
	options: {
		terms: 'dt',
		definitions: 'dd'
	},
	initialize: function(container, options) {
		//memorizziamo un riferimento al contenitore
		this.container = $(container);
		//setOptions è un metodo messo a disposizione dal mixin Options,
		//serve a "fondere" le opzioni di default con quelle passate al metodo
		this.setOptions(options);
		//memorizziamo termini e definizioni
		this.terms = this.container.getElements(this.options.terms);
		this.definitions = this.container.getElements(this.options.definitions);
		//chiamiamo il metodo attach
		//separare questo metodo dal costruttore rende la nostra classe più facile da estendere
		this.attach();
	},
	attach: function(){
		//cicla sui termini
		this.terms.each(function(term, index) {
			//aggiungi l'evento click a ognuno
			term.addEvent('click', function(){
				//il click chiama il metodo toggle sull'indice corrente
				this.toggle(index);
			}, this);
		}, this);
	},
	toggle: function(index){
		//apre la definizione per l'indice passato
		this.definitions[index].slide('toggle');
	}
});

Però! È un sacco di codice. Anche se togliessimo tutti i commenti, sarebbero ancora due dozzine di linee. Ho già mostrato come sia possibile realizzare questo plugin con più o meno la stessa quantità di codice della versione jQuery. Allora perché questa versione è così tanto lunga? Beh, l'abbiamo resa molto più flessibile. Per usare la classe ci basta chiamare il costruttore, in questo modo.

var myFAQ = new FAQ(myContainer);
//volendo ora possiamo chiamare dei metodi:
myFAQ.toggle(2); //mostra il terzo elemento

Possiamo accedere a metodi e proprietà dell'istanza. E la nostra versione ajax? Il problema con jQuery era che non potevamo ritardare l'apertura animata della definizione aspettando il completamento della richiesta ajax. Non abbiamo più questo problema con MooTools:

FAQ.Ajax = new Class({
	//questa classe eredita le proprietà della classe FAQ
	Extends: FAQ,
	//aggiungiamo anche un'altra opzione a quelle di default.
	//questa à l'url, alla quale aggiungeremo l'indice del
	//termine; veramente potremmo fare qualcosa di più robusto,
	//ma come esempio va bene così
	options: {
		url: null;
	},
	//memorizzeremo i risultati delle chiamate ajax, così se una sezione verrà
	//aperta due volte non avremo bisogno di interrogare nuovamente il server
	indexesLoaded: [],
	toggle: function(index){
		//se abbiamo già caricato la definizione
		if (this.indexesLoaded[index]) {
			//ci basta chiamare la versione precedente di toggle
			this.parent(index);
		} else {
			//altrimenti, invia la richiesta al server
			new Request.HTML({
				update: this.definitions[index],
				url: this.options.url + index,
				//e quando i dati sono pronti, espandi la definizione
				onComplete: function(){
					this.indexesLoaded[index] = true;
					this.definitions[index].slide('toggle');
				}.bind(this)
			}).send();
		}
	}
});

Ora abbiamo una versione della classe FAQ che ci permette di caricare le definizioni dal server. Da notare che siamo riusciti ad integrare la nuova logica in modo da non far apparire la definizione prima della ricezione del contenuto dal server (cosa che non potevamo fare nella versione jQuery). Da notare anche come sia stato necessario descrivere solamente la nuova funzionalità (ajax) e poco altro. Questa estensibilità rende possibile la creazione di famiglie di plugin che offrono diverse sfumature di medesime funzionalità. Ciò significa anche che possiamo usare plugin scritti da altri e modificarne solo quelle piccole parti che vogliamo diverse (senza dover duplicare tutto). Questo spiega perché, data una certa funzione - un selettore di date, una interfaccia a tabs, etc - si trovano pochi plugin per MooTools. Molti plugin infatti risolvono il problema oppure, se non lo fanno, è possibile estenderli facilmente aggiungendo quello che serve.

Come ho illustrato prima, è possibile scrivere widget complessi con jQuery, con metodi e stato. Gran parte del codice scritto facendo ciò è JavaScipt puro quando c'è necessita di esprimere logiche non relative al DOM. Ma il modello di jQuery non offre un sistema per estendere queste istanze in sottoclassi. E nemmeno aiuta con mixin riutilizzabili facilmente. Infine, i plugin di jQuery sono sempre collegati a elementi del DOM. Se si vuole scrivere una classe che, diciamo, elabora degli URL, non c'è un sistema con stato per fare cose di questo tipo, a meno che non lo si scriva appositamente.

È ora di decidere

jQuery mira all'espressività, alla scrittura rapida e veloce di codice e al DOM, mentre MooTools mira ad estendibilità, ereditarietà, leggibilità, riuso del codice e mantenibilità. Puoi vederli come due punti opposti di una scala. Da una parte jQuery è qualcosa che ti permette di iniziare con semplicità ed avere subito il risultato, ma (per mia esperienza) può trasformarsi in codice difficile da riutilizzare e mantenere (o meglio, questa è una tua responsabilità, non un problema di jQuery in sè). Dall'altra parte MooTools necessita di più tempo per imparare e richiede più codice prima che tu possa vedere i risultati del tuo lavoro, ma successivamente quello stesso codice sarà più riusabile e più mantenibile.

Né il nucleo (core) di MooTools né quello di jQuery forniscono tutte le funzionalità immaginabili. Entrambi i framework mantengono un nucleo piuttosto snello, lasciando a te e agli altri sviluppatori la possibilità di creare plugin ed estensioni. Il loro obiettivo non è quello di darti ogni funzionalità di cui potresti avere bisogno, ma quello di darti gli strumenti affinchè tu possa implementare qualasiasi cosa ti passi per la testa. È questo il punto di forza dei framework JavaScript tutti, ed entrambi eccellono in questo. MooTools ha un approccio più olistico, fornisce gli strumenti per scrivere qualunque cosa, andando oltre il DOM, ma pagando il prezzo di una ripida curva di apprendimento. L'approccio estensibile e olistico di MooTools ti fornisce tutte le funzionalità di jQuery e oltre, ma jQuery si focalizza su una eccellente interfaccia per il DOM e comunque non ti impedisce di usare i meccanismi di ereditarietà nativi del JavaScript o di usare un sistema di classi simile a quello di MooTools, se lo vuoi.

Ecco perché dico che entrambi i framework sono ottime scelte. Il mio sforzo voleva essere quello di evidenziare le differenze filosofiche tra le due librerie ed evidenziarne vantaggi e svantaggi. Dubito di esser riuscito a tenere la mia simpatia per MooTools completamente sotto controllo, ma spero di esser stato d'aiuto. A prescindere dal framework che sceglierai, ora conosci sicuramente meglio entrambi. Se hai il lusso del tempo, ti consiglio vivamente di realizzare un sito con ciascuno. Potrai scrivere il tuo parere su entrambi e magari il tuo punto di vista potrà mettere in luce qualche aspetto che ho trascurato.

La cronologia di questo documento è consultabile su github.


Chi sono: sono un collaboratore MooTools e ho un blog su JavaScript e altro sul mio sito Clientcide, insieme a numerosi plugin per MooTools. Sono l'autore di MooTools Essentials e del tutorial di MooTools. Lavoro in una società nella baia di San Francisco chiamata Cloudera. Puoi contattarmi qui.


Una nota sui commenti: I commenti sono moderati. Nessun commento verrà pubblicato prima di essere controllato. Commenti non costruttivi (polemici, sgarbati, ecc) non saranno approvati. Allo stesso modo, commenti da "tifoso" non saranno accettati - ad esempio "Il FrameworkX spacca! Sicuramente meglio di FrameworkY!" non è considerato un commento costruttivo.