Come ho già detto nei precedenti articoli, Introduzione a MooTools e Le basi di MooTools, MooTools è un framework completamente orientato agli oggetti che permette di creare meccanismi di ereditarietà complessi sfruttando i potenti metodi nativi del costruttore Class. In questo articolo vedremo come creare un meccanismo di ereditarietà utilizzando due dei tre oggetti extra disponibili per Class: l'oggetto Options e l'oggetto Events.
OOP: superclassi e sottoclassi
Un'applicazione orientata agli oggetti definisce due grandi tipologie di costruttori: le superclassi e le sottoclassi.
Le prime sono gli oggetti "genitori" delle seconde, sono generalmente il più astratte possibile e contengono metodi "core" che potranno essere utilizzati dalle relative sottoclassi. Le sottoclassi ereditano tutti i metodi della classe "genitore" e in più possono ridefinirli con nuove funzionalità.
Vediamo brevemente un esempio di superclasse e sottoclasse in MooTools:
var SuperClass = new Class({
initialize: function() {
// inizializzazione
}
});
var SubClass = SuperClass.extend({
// metodi di SubClass
});
Grazie al metodo EXTEND
tutti i metodi della classe SuperClass vengono ereditati dalla classe SubClass (per una trattazione completa dell'oggetto Class e dei suoi metodi rimando all'articolo Le basi di MooTools).
Con MooTools è possibile fare molto di più rispetto ad un classico meccanismo di ereditarietà. Ad esempio, è possibile estendere le caratteristiche degli oggetti Class con funzionalità extra tramite gli oggetti Options, Events e Chain (Class.Extras.js).
Questi possono essere implementati in qualsiasi oggetto di tipo Class con l'ausilio del metodo IMPLEMENT
:
myClass.implement(new Chain, new Events, new Options);
Gli stessi sviluppatori di MooTools implementano questi oggetti extra in diversi altri oggetti importanti del framework, come Fx, XHR e Ajax.
L'oggetto Options
Con l'oggetto Options implementato possiamo decentralizzare i dati di un oggetto Class nelle opzioni del costruttore, o meglio, nella sua proprietà this.options
. Vediamo subito un esempio. Assumiamo di avere un oggetto di tipo Class chiamato Car che rappresenta una macchina. Prima dell'inizializzazione dichiariamo una proprietà chiamata options
e impostiamo a tutte le proprietà i nostri valori di default:
var Car = new Class({
options: {
numeroPorte: 4,
alimentazione: 'diesel'
},
initialize: function(name, options) {
this.name = name;
this.setOptions(options);
}
});
I nostri oggetti di tipo Car
avranno due opzioni di default: il numero delle porte della macchina (di default 4) e il tipo di alimentazione (di default 'diesel'). Ricordo che il precedente esempio non funzionerà se non scriviamo la seguente riga di codice:
Car.implement(new Options);
Nell'initialize facciamo uso del metodo setOptions
ereditato dall'oggetto Options
, con il quale è possibile settare la proprietà this.options
di Car
, le cui proprietà passate al costruttore sovrascriveranno quelle di default:
var myCar = new Car('myCar', {alimentazione: 'benzina', numeroPorte:3});
// 'benzina'
alert(myCar.options.alimentazione);
Questa tecnica di decentralizzazione apporta notevoli vantaggi:
- la possibilità di definire parametri di default modificabili dall'utente all'inizializzazione del costruttore, in modo da personalizzare istanze aventi lo stesso costruttore;
- una sintassi molto più elegante e flessibile. Pensate se
Car
avesse dieci opzioni e, invece di dichiararle nella proprietà options, ovvero:
var myCar = new Car('myCar', {
opt1 = 'opt1',
opt2 = 'opt2',
opt3 = 'opt3',
opt4 = 'opt4',
});
avessimo dovuto scrivere:
var myCar = new Car('myCar', 'opt1', 'opt2', 'opt3', 'opt4', ...);
Sarebbe un totale fallimento se l'utente saltasse la dichiarazione di un parametro.
Il mio consiglio è di includere nelle opzioni tutte le proprietà 'secondarie' che personalizzano il nostro costruttore e di lasciare come normali parametri dati principali come l'id di un elemento e cosi via.
Come vedremo, oltre alle proprietà, è possibile includere nelle opzioni anche i metodi, che rappresentano gli eventi da richiamare in determinati momenti all'interno del costruttore.
L'oggetto Events
Se un oggetto Class
implementa l'oggetto Events
, assume la capacità di settare, azionare e rimuovere eventi.
Un evento viene inizializzato passando alla proprietà options
vista in precedenza un metodo che inizia con 'on' seguito da una lettera maiuscola:
options: {
numeroPorte: 4,
alimentazione: 'diesel',
onStart: function() {
alert('Macchina accesa');
},
onFinish: function() {
alert('Macchina spenta');
}
}
In questo caso abbiamo impostato due eventi, onStart
e onFinish
, le cui funzioni di default sono quelle di avvisare che la macchina viene rispettivamente accesa o spenta. Vediamo ora come azionarli:
Car.implement({
avvio: function() {
this.fireEvent('onStart');
},
spegnimento: function() {
this.fireEvent('onFinish');
}
});
Se richiamiamo il metodo avvio
su di un oggetto di tipo Class
, l'evento onStart
verrà azionato e verrà visualizzato il messaggio:
// alert 'Macchina accesa'
myCar.avvio();
Possiamo personalizzare l'azione dell'evento onStart
passando alla proprietà options
un nuovo metodo onStart
che sovrascriverà le funzionalità di quello di default:
options: {
onStart: function() {
$('myBox').setHTML('Avvio');
}
}
Ora, se richiamiamo myCar.avvio
, verrà inserito il testo 'Avvio' nell'elemento con id 'myBox'.
L'oggetto Events
aggiunge al nostro costruttore anche il metodo removeEvent
, che funziona come fireEvent
ma rimuove l'evento invece che azionarlo, ed il metodo addEvent
che aggiunge un evento allo stack degli eventi dell'istanza in considerazione.
Un'altra funzionalità aggiuntiva dell'oggetto Events
molto importante è che è possibile passare ai metodi che rappresentano gli eventi uno o più parametri. Nel secondo caso è necessario passare un array di argomenti:
onStart(property) {
alert(property);
}
// azioniamolo...
this.fireEvent('onStart', this.genericproperty);
Meccanismo di ereditarietà
Ora che abbiamo visto come funziona il meccanismo di creazione e di gestione degli oggetti di tipo Class, vediamo come estendere l'oggetto precedentemente creato, Car
, con funzionalità nuove rappresentate da un nuovo oggetto chiamato SuperCar
:
var Car = new Class({
options: {
numeroPorte: 4,
alimentazione: 'diesel',
onStart: function() {
alert('Macchina accesa');
},
onFinish: function() {
alert('Macchina spenta');
}
},
initialize: function(name, options) {
this.name = name;
this.setOptions(options);
},
avvio: function() {
this.fireEvent('onStart');
},
spegnimento: function() {
this.fireEvent('onFinish');
}
});
Car.implement(new Events, new Options);
var SuperCar = Car.extend({
options: {
numeroMarce: 6,
velMax: 320,
onChange: function(num) {
alert('Marcia aumentata: sei in ' + num);
},
onError: function(num) {
alert('Non puoi inserire più di ' + num + ' marce');
}
},
initialize: function(name, options) {
this.parent(name, options);
this.marcia = 0;
},
aumenta: function() {
if(this.marcia < this.options.numeroMarce) {
this.marcia ++;
this.fireEvent('onChange', this.marcia);
}
else this.fireEvent('onError', this.options.numeroMarce);
}
});
L'oggetto SuperCar
eredita tutti i metodi e le opzioni dell'oggetto Car
, in più, definisce tre nuove opzioni:
- l'evento
onChange
che scatta quando aumentiamo la marcia; - l'evento
onError
che dice che non è possibile inserire un numero di marce superiore a quelle dichiarate nell'opzionenumeroMarce
; - la velocità massima.
Ho creato questo esempio per mostrare tutta la potenza e la flessibilità degli oggetti Class. Ovviamente questo non è tutto quello che è possibile fare, basta dare un'occhiata ai meccanismi degli effetti Fx per comprenderne le infinite potenzialità.