Per utilizzare al meglio le funzionalità avanzate offerte da MooTools, per crearne di nuove e per gestire i diversi aspetti della nostra applicazione, è necessario comprendere il meccanismo fondamentale che sta alla base di questa libreria.
In questa prima parte dell'articolo vedremo come utilizzare i cosiddetti componenti "core", mentre nella seconda impareremo a svolgere operazioni con il costruttore Class, il "motore" di MooTools. Vi ricordo che la documentazione completa del framework è disponibile all'indirizzo http://docs.mootools.net.
$native e $extend
Iniziamo con l'analisi dei componenti di base (contraddistinti dal simbolo di dollaro '$' iniziale), che svolgono compiti di fondamentale importanza e vengono utilizzati per la creazione di molti altri componenti.
Tra di essi è presente una funzione interna chiamata $native
, che, come indica il nome, agisce sugli oggetti nativi di Javascript (Array, String, Function e Number) aggiungendo ad ognuno di essi un metodo chiamato "extend
".
Utilizzando questo metodo è possibile aggiungere nuove proprietà all'oggetto prototype del nostro oggetto principale, ma a differenza del Javascript nativo, o di altri framework quali jQuery e Prototype, è possibile farlo in uno stile decisamente più "object oriented".
Vediamo subito un semplice esempio. Supponiamo di volere aggiungere al costruttore String
un metodo chiamato "firstChar
", che restituisce il primo carattere della stringa. Ogni oggetto in Javascript, possiede una proprietà chiamata "prototype
" che è un ulteriore oggetto contenente i metodi del costruttore principale.
In Javascript nativo bisogna quindi impostare una nuova proprietà nell'oggetto prototype, in modo che possa essere utilizzata dal costruttore principale:
String.prototype.firstChar = function() {
return this.charAt(0);
}
La chiamata seguente del metodo firstChar
restituisce il primo carattere della parola, ovvero "c":
"ciao".firstChar();
MooTools permette di svolgere questa stessa operazione in una modalità più vicina ai modelli orientati agli oggetti di C++, Java e PHP5: basta applicare il metodo extend
all'oggetto desiderato e passargli come parametro un letterale oggetto contenente i metodi da aggiungere:
String.extend({
firstChar: function() {
return this.charAt(0);
}
});
Il codice precedente aggiunge il metodo firstChar
all'oggetto prototype di String
, nello stesso modo in cui accadeva nel primo esempio.
Utilizzando questo approccio è possibile aggiungere un qualsiasi numero di metodi agli oggetti nativi, effettuando una sola chiamata del metodo extend
, in maniera molto più rapida rispetto a quella predefinita.
Per citare un ulteriore esempio, nei file 'php_string', ' utility_string' e' utility_array' di Moo.Rd, ho utilizzato il metodo extend
per aggiungere una numerosa collezione di metodi ai costruttori String e Array.
Il risultato è un codice molto più pulito ed intuitivo.
Un'altra funzione di base molto importante è $extend
, che permette di "fondere" due oggetti, aggiungendo alle proprietà del primo quelle del secondo (sovrascrivendo quelle già esistenti).
Ecco un esempio:
var first = {
'name': 'firstName',
'number': 'firstNumber'
};
var second = {
'number': 'secondNumber',
'address': 'secondAddress'
};
$extend(first, second);
L'oggetto first
ha guadagnato tutte le proprietà dell'oggetto second
:
{
'name': 'firstName',
'number': 'secondNumber',
'address': 'secondAddress'
};
L'utilizzo di questa funzione è basilare per applicazioni orientate agli oggetti che si basano sui principi dell' ereditarietà.
Altre funzioni core
Come abbiamo sottolineato nel primo articolo, MooTools è un framework completamente orientato agli oggetti, che a differenza di molti altri, esprime tutte le sue potenzialità in applicazioni OOP. È quindi indispensabile fare pratica con questo tipo di funzioni prima di utilizzare i componenti più avanzati.
Le funzioni $defined
, $pick
e $chk
sono funzioni "di controllo", permettono cioè di controllare il valore di una variabile. Per questo motivo vengono solitamente utilizzate nei costruttori condizionali, come if
.
$defined
restituisce true
se l'oggetto passato come parametro è definito, cioè non è null o indefinito:
var ok = 'ok';
var undef = null;
// restituisce true
$defined(ok);
// restituisce false
$defined(undef);
$pick
accetta due oggetti come parametri e restituisce il primo se è definito, altrimenti il secondo. Questa funzione è utile per eseguire controlli con dati di input, e restituire messaggi di default solo nel caso in cui il dato non sia definito:
// se username è definito (ad esempio contiene il valore 'Silver'), viene mostrato il messaggio 'Ciao Silver!', altrimenti 'Ciao utente!'
function controlUser(username) {
alert('Ciao' + $pick(username, 'utente') + '!');
}
$chk
restituisce true
se il valore o l'oggetto passatogli come parametro è definito oppure è pari a 0.
Un' altra funzione di controllo molto utile è $type
, che permette di ottenere (come stringa) il tipo del dato passatogli come parametro. A differenza di una normale funzione che svolge questa operazione, $type
consente di controllare tra ben 13 tipi di dato differenti, anche non presenti in Javascript nativo, come 'class', 'arguments' o 'collection'. Ecco perché il suo utilizzo è sempre consigliato in sostituzione dei normali controlli (ad esempio if(typeof(myValue) == 'String')
)
Vediamo in dettaglio tutti i valori che può restituire:
valore | se l'oggetto è... | esempio |
'element': | un elemento DOM | div, a, b, pre |
'textnode': | un nodo DOM di tipo TextNode | il testo contenuto in un elemento div |
'whitespace': | un nodo whitespace DOM | spazi bianchi negli elementi |
'arguments': | un oggetto arguments | gli argomenti di una funzione |
'object': | un oggetto | var obj = { }; |
'string': | una stringa | var phrase = 'Ciao'; |
'number': | un numero | 24 |
'function': | una funzione | var myFunction = function() {...}; |
'boolean': | un valore booleano | true o false |
'regexp': | un espressione regolare | var regexp = new RegExp(...); |
'class': | un oggetto Class | var myClass = new Class(...); |
'collection': | una collezione nativa di elementi DOM | getElementsByTagName |
false: | non è definito o false | false |
Tra le funzioni di base troviamo inoltre:
la funzione $random
, che restituisce un numero random il cui valore è compreso tra i due che gli vengono passati:
$random(2, 98);
la funzione $time
che restituisce un timestamp intero. Viene utilizzata internamente per il calcolo dellla durata degli effetti Fx;
la funzione $clear
interrompe il timeout o l'interval (impostato in precedenza con i metodi delay o periodical) passatogli come parametro:
function welcome() {
alert('Benvenuto');
}
// attende un secondo e mostra il messaggio di benvenuto
var timer = welcome.delay(5000);
// azzera il timeout
$clear(timer);
L' ultima funzione core presente nella versione 1.1 di MooTools è $merge
, che svolge una funzionalità simile a $extend
: permette di unire ricorsivamente un qualsiasi numero di oggetti esistenti in un unico nuovo oggetto che viene restituito. Gli oggetti originali non verranno alterati come accade con $extend
.
var obj1 = {
'nome': 'Silver',
'numero': '12345678'
};
var obj2 = {
'nazione': 'America',
'paese': 'New York'
};
var obj3 = {
'nome': 'Gold'
};
var newObj = $merge(obj1, obj2, obj3);
/*
newObj diventa {
'nome': 'Gold',
'numero': '12345678',
'nazione': 'America',
'paese': 'New York'
}
*/
L'oggetto newObj
contiene tutte le proprietà degli oggetti con cui è stato creato, e questi non sono stati modificati.
Oltre a mettere a disposizione queste importanti funzioni, il file core.js (che deve sempre essere inserito nella crezione del download) estende l'oggetto window
nativo con diverse proprietà di controllo, per verificare la tipologia del browser che sta correntemente eseguendo lo script.
Vediamole in dettaglio (a sinistra la proprietà, a destra il browser corrispondente):
window.ie
: Internet Explorer (qualsiasi versione)
window.ie6
: Internet Explorer 6
window.ie7
: Internet explorer 7
window.gecko
: Mozilla/Gecko.
window.webkit
: Safari/Konqueror
window.webkit419
: Safari2 (webkit versione 419)
window.webkit420
: Safari3 (Webkit SVN Build) o web kit versioni superiori a 419
window.opera
: Opera
Esempio:
if(window.ie7) alert('Stai usando Internet Explorer 7');
else if(window.gecko) alert('Stai usando Mozilla Firefox');
È molto utile utilizzare questi tipi di controllo per creare applicazioni cross-browser, senza avere la necessità di fare dei test personali come if(window.ActiveX)
ecc...
Inoltre, grazie a queste proprietà, è possibile avere una panoramica non solo tra i browser differenti, ma anche tra le varie versioni dello stesso browser.
Nella versione 1.2 di MooTools tuttavia, queste nuove proprietà verranno tolte dall'oggetto window
e inserite in nuovo oggetto chiamto Client
, e più precisamente nella sua proprietà Engine
, che rappresenta la tipologia di browser in uso.
Class
Il "motore" che fa girare tutto il framework, è racchiuso nell'oggetto Class, (qui la documentazione), e nei suoi metodi nativi.
Utilizzando questo costruttore, è possibile creare applicazioni semplici (come la gestione di oggetti in stile OOP) e più complesse: gli stessi sviluppatori utilizzano Class per creare ogni altro componente, dai numerosi effetti Fx, ai costruttori Ajax, Drag e cosi via.
L'oggetto Class permette di creare vere e proprie gerarchie di oggetti, in una maniera simile ai linguaggi completamente orientati agli oggetti, grazie ai suoi metodi "extend
" ed "implement
".
Per comprendere il suo meccanismo, partiamo da un semplice esempio di dichiarazione di un oggetto in Javascript nativo:
var myObj = function(name) {
this.name = name;
this.setName = function(name) {
this.name = name;
}
};
var obj = new myObj('myObj');
// alert 'myObj'
alert(obj.name);
In questa maniera, è possibile creare rapidamente un oggetto ed assegnarli un qualsiasi numero di proprietà, ma non è facilmente possibile creare un meccanismo solido di ereditarietà, in modo che un altro oggetto possa ereditare le sue proprietà ed i suoi metodi.
È proprio per questo motivo che gli sviluppatori di MooTools hanno creato l'oggetto Class.
Il suo approccio nella creazione di oggetti è molto differente da quelllo nativo. Innanzitutto, se vogliamo creare un oggetto come quello precedente con il meccanismo di MooTools, questo deve essere di tipo "Class":
var myObj = new Class({});
Gli oggetti dichiarati in questo modo accettano come parametro un letterale oggetto che deve contenere tutte le proprietà ed i metodi desiderati.
A differenza della dichiarazione normale, negli oggetti Class l'inizializzazione delle proprietà viene effettuata da un apposito metodo chiamato appunto "initialize
", e a cui devono essere passati gli stessi argomenti che verrebbero passati al costruttore:
// se ad esempio vogliamo creare un costruttore come quello seguente:
var user = new User('Silver', 28);
// l'oggetto di tipo Class chiamato User deve essere dichiarato nel seguente modo:
var User = new Class({
initialize: function(name, age) {
this.name = name;
this.age = age;
}
});
A questo punto, tutte le istanze dell' oggetto User guadagnano i metodi nativi di Class, ovvero extend
ed implement
.
Questi svolgono funzionalità simili, cioè entrambi permettono di aggiungere nuovi metodi all'oggetto, ma il mio consiglio è di utilizzare extend
se si vuole creare una gerarchia, cioè nuovi oggetti in cui vengono copiate tutte le proprietà dell'oggetto che estendono.
Se invece si vuole solamente aumentare il numero di metodi, è opportuno utilizzare implement
.
Supponiamo ad esempio di volere aggiungere al costruttore User creato nell'esempio precedente due nuovi metodi: uno chiamato setName
che permette di impostare un valore alla proprietà name
, e uno chiamato setAge
che svolge la stessa funzione ma con la proprietà age
.
In questo caso richiamiamo implement
e gli passiamo un oggetto che contiene questi metodi.
Questo provvederà ad aggiungerli all'oggetto prototype di User utilizzando la funzione $extend
che abbiamo studiato in precedenza:
User.implement({
setName: function(name) {
this.name = name;
},
setAge: function(age) {
this.age = age;
}
});
Un particolare importante: prima abbiamo affermato che la funzione $extend
sovrascrive le proprietà esistenti con quelle nuove che hanno lo stesso nome, e dato che implement
si avvale di questa funzione per aggiungere metodi all'oggetto prototype, le proprietà già presenti verranno sostituite con quelle nuove aventi nomi equivalenti.
Ora, avendo esteso l'oggetto User con nuovi metodi, è possibile fare:
// il nome di user viene cambiato da 'Silver' a 'Gold'
user.setName('Gold');
e cosi via.
Come vedremo più avanti inoltre, nei componenti avanzati di MooTools viene utilizzato implement
per aggiungere agli oggetti Class (come i costruttori Fx e Ajax) le proprietà degli oggetti presenti nel file Class.Extras.js, ovvero Chain, Events e Options, che svolgono funzioni fondamentali, come la creazione di catene, l'aggiunta di eventi e la possibilità di definire le opzioni.
Un ulteriore esempio dell'uso di implement
è dato dai file Table.js, Tablecols.js e Tablecols2.js di Moo.Rd.
Grazie ad implement
, ho potuto suddividere i metodi del costruttore Table in tre file separati, in modo da favorire la modularità e diminuire il peso dell'applicazione complessiva: in Table.js viene dichiarato il costruttore Table, mentre negli altri due file viene esteso con nuove funzionalità.
Il secondo metodo nativo dei costruttori Cass, extend
, copia tutte le proprietà dell'oggetto "genitore", ovvero la superclasse nell'oggetto che lo estende, ovvero la sua sottoclasse.
In parole semplici, se l'oggetto A estende l'oggetto B, eredita tutte le proprietà di quest'utlimo, tranne quelle che già possiede con nomi equivalenti.
Questo meccanismo simula molto bene le caratteristiche dei linguaggi OOP, e permette di creare delle gerarchie di oggetti anche molto complesse.
Vediamo un esempio:
// superclasse
var Car = new Class({
initialize: function(marca, targa) {
this.marca = marca;
this.targa = targa;
},
setMarca: function(marca) {
this.marca = marca
},
setTarga: function(targa) {
this.targa = targa
},
setCilindrata: function(cilindrata) {
this.cilindrata = cilindrata;
}
});
// classe che eredita "SuperCar extends Car"
var SuperCar = Car.extend({
initialize: function(marca, targa) {
this.marca = marca;
this.targa = targa;
this.cilindrata = 4000;
}
});
var supercar = new SuperCar('Ferrari', 'numeroTarga');
// utilizzo dei metodi ereditati da Car
supercar.setCilindrata(4500);
La Class Car, è un costruttore generico che contiene tutti i metodi "di costruzione" per un oggetto di tipo Car o che eredita da Car, mentre l'oggetto SuperCar è una sua sottodivisione, per macchine con cilindrata pari a 4000.
L'utilità di questo meccanismo gerarchico è evidente: possono esistere molti altri sottotipi del costruttore Car, ad esempio Utilitaria, MonoVolume, Suv, ecc, ma se per ognuno avessimo dovuto creare gli stessi metodi, oltre che a un dispendio di memoria considerevole, avremmo ottenuto un codice davvero difficile da mantenere.
Nel nostro caso, se dobbiamo cambiare il compito svolto dal metodo setMarca
, basta modificare il metodo nel suo costruttore generico Car, in modo che la modifica sia visibile in tutti i costruttori che ereditano, invece che modificarlo in tutti i sottocostruttori.
Credo che da tutto ciò si sia intuito come Extend
sia un metodo indispensabile nell'utilizzo del framework MooTools.
Per la parte di base e teorica ci siamo, e questa panoramica era comunque necessaria. Nei prossimi appuntamenti vedremo come creare e gestire effetti passando così a un po' di pratica.