Nella prima parte dell'articolo, abbiamo affrontato l'impronta teorica comprendente le considerazioni e i consigli più utili per intraprendere lo sviluppo di una piccola libreria di codice Javascript. È ora di passare alla parte pratica e vedere con quale approccio affreontare questo tipo di soluzione.
Classe Element
La nostra libreria di codice si occuperà prevalentemente di lavorare con gli elementi DOM della pagina. I compiti più immediati che si possono svolgere con questi ultimi sono il recupero, la manipolazione e il setting/getting di attributi HTML e stili CSS. Abbiamo affermato che la nostra libreria assumerà un approccio Object Oriented, dato che Javascript offre tutti gli strumenti adatti alla creazione di involucri di dati molto evoluti.
La prima classe che andremo a creare si chiamerà dunque Element e ci permetterà di effettuare tutte queste operazioni con gli elementi utilizzando una sintassi comune. Ecco l'aspetto del primissimo metodo, chiamato get, che permette di recuperare gli elementi basandosi sull'id degli stessi:
/*
Class: Element
Permette di lavorare con gli elementi DOM della pagina
*/
var Element = {
/*
Metodo: get
Restituisce un elemento DOM basandosi sull'id dello stesso
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
get: function(id) {
return document.getElementById(id);
}
}
// utilizzo del metodo Element::get
window.onload = function() {
console.log(Element.get('myEl_1'));
}
A questo punto potremmo già effettuare operazioni del tipo setting e getting di stili CSS nel seguente modo standard:
Element.get('myEl_1').style.color = 'red';
ma uno degli scopi della nostra libreria è quello di avere una sintassi intuitiva e comune, senza contare il fatto che dobbiamo riuscire a scrivere meno codice di quanto facciamo normalmente. Ecco dunque i nuovi metodi getStyle e setStyle, che si occuperanno rispettivamente di restituire ed impostare gli stili inline css di un elemento:
var Element = {
// ...
/*
Metodo: getStyle
recupera il valore dell stile inline di una proprietà css specificata
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [string] css: la proprietà css
*/
getStyle: function(id, css) {
return Element.get(id).style[css];
},
/*
Metodo: setStyle
imposta lo stile inline ad una proprietà css specificata
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [string] css: la proprietà css
- [string] value: il valore della proprietà css
*/
setStyle: function(id, css, value) {
Element.get(id).style[css] = value;
}
}
// utilizzo del metodo Element::get
window.onload = function() {
Element.setStyle('myEl_1', 'color', 'red');
// 'red'
console.log(Element.getStyle('myEl_1', 'color'));
}
Come possiamo vedere, siamo ora in grado di recuperare ed impostare uno alla volta gli stili CSS di un elemento. Tuttavia, potrebbe essere molto noioso chiamare più volte lo stesso metodo per effettuare lo stessa task. Supponiamo di voler impostare tre stili CSS differenti al nostro elemento: con l'aspetto attuale dell'oggetto Element
dovremmo obbligatoriamente scrivere:
Element.setStyle('myEl_1', 'color', 'red');
Element.setStyle('myEl_1', 'display', 'block');
Element.setStyle('myEl_1', 'fontSize', '14px');
Ricordiamo che una libreria non raggiunge mai lo stato dell'arte, ma deve essere continuamente aggiornata e migliorata per adattarsi al meglio alle nostre esigenze. Per questo motivo, andiamo a creare il metodo setStyles, che ci permetterà di impostare più stili CSS al nostro elemento con una sola chiamata:
var Element = {
/*
Metodo: setStyles
imposta lo stile inline ad una moltitudine di proprietà css specificate
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [object] css: le proprietà css da impostare
*/
setStyles: function(id, css) {
for (var p in css) {
Element.get(id).style[p] = css[p];
}
}
}
Andiamo ora a ripetere il precedente task, ma avvalendoci del nuovo metodo:
Element.setStyles('myEl_1', {'color': 'red', 'display': 'block', 'fontSize': '14px'});
Et voilà, il gioco è fatto.
Migliorare la classe Element
Anche se potrebbe sembrare che la classe Element
sia perfetta, in realtà non è cosi. Esistono ben due aspetti che devono essere assolutamente migliorati:
- non è attualmente possibile concatenare più chiamate ai suoi metodi. Ad esempio:
Element.setStyle('myEl', 'color', 'red').setStyle('myEl', 'display', 'block');
- non esiste la possibilità di mettere internamente in cache l'elemento, ma viene ogni volta recuperato nuovamente. Questa strategia consuma molte più risorse del dovuto.
Andiamo dunque ad apportare le suddette modifiche alla classe (in questo snippet tralasciamo i commenti della documentazione relativi ai precedenti metodi):
var Elements = [];
var Element = {
/*
Metodo: cache
Mette nella cache un elemento DOM (metodo privato)
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
cache: function(id) {
if(!Elements[id]) Elements[id] = document.getElementById(id);
return Elements[id];
},
get: function(id) {
return Element.cache(id);
},
getStyle: function(id, css) {
return Element.cache(id).style[css];
},
setStyle: function(id, css, value) {
Element.cache(id).style[css] = value;
return Element;
},
setStyles: function(id, css) {
for (var p in css) {
Element.cache(id).style[p] = css[p];
}
return Element;
}
}
Il metodo di caching che ho adottato per questo esempio è efficace e brutalmente immediato allo stesso momento. In pratica, ogni volta che effettuiamo operazioni con un elemento, questo verrà per la prima volta inserito in apposito array (Elements
in questo caso) e successivamente recuperato dalla medesima postazione.
Ora sarà inoltre possibile concatenare più chiamate contemporaneamente:
Element.setStyles('myEl_1', {'color': 'red', 'display': 'block', 'fontSize': '14px'}).setStyles('myEl_1', {'marginLeft': '20px'});
Shortcuts
Quando sviluppiamo internamente, per quanto elegante e compatta possa essere la nostra sintassi, arriverà il momento che si sentirà la necessità di accorciare i tempi, soprattutto per quanto riguarda la produzione di test locali e snippet minimali. Per questo motivo, andiamo a creare uno shortcut chiamato semplicemente $
, che si occuperà di recuperare un elemento DOM e di metterlo in cache:
/*
Function: $
Recupera un elemento DOM basandosi sull'id
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
var $ = function(id) {
return Element.get(id);
}
// utilizzo
console.log($('myEl_1'));
console.log($('myEl_2'));
A questo punto la nostra classe Element
ha tutto ciò occorre per essere considerata stabile. Occorrerà ovviamente ampliarla con tanti nuovi metodi che andranno a costituire la nostra API.
Classe Window
Come avete notato, all'interno di alcuni snippet precedenti ho utilizzato l'evento onload dell'oggetto window per effettuare le operazioni descritte. Questa sintassi minimale presenta 2 svantaggi:
- non è possibile impostare più eventi allo stesso oggetto
window
; - la sintassi troppo minimale contrasta con la più elegante ed intuitiva API di Element.
Per questo, andiamo a creare la nostra classe Window
che ci permetterà di aggiungere una moltitudine di eventi all'oggetto window con una sintassi che meglio si adatta alle nostre esigenze:
/*
Class: Window
Wrapper per l'oggetto window
*/
var Window = {
/*
Metodo: load
imposta l'evento load
*/
load: function(fn) {
window.addEventListener('load', fn, false);
}
}
Andiamo ora a riscrivere gli snippet precedenti con la nuova sintassi:
Window.load(function() {
Element.setStyle('myEl_1', 'color', 'red');
// 'red'
console.log(Element.getStyle('myEl_1', 'color'));
});
Window.load(function() {
console.log($('myEl_1'));
console.log($('myEl_2'));
});
Finalizzare la libreria
Una volta terminata la versione stabile della nostra libreria personale, è giunta l'ora di finalizzare il lavoro rilasciando la nostra nuova release, nel caso si tratti di un lavoro pubblico. In ogni caso, è sempre bene fornire i nostri codici di una determinata licenza e del nome dell'autore (o degli autori). In questo caso sceglieremo la licenza MIT:
/*
HTML.it Element API Version 1.0
Author: Riccardo Degni AKA RD
License: MIT-style License
*/
// ...
L'aspetto finale della nostra libreria sarà dunque il seguente:
/*
HTML.it Element API Version 1.0
Author: Riccardo Degni AKA RD
License: MIT-style License
*/
/*
Array: Element
Wrapper che simula la cache di elementi.
*/
var Elements = [];
/*
Class: Element
Permette di lavorare con gli elementi DOM della pagina
*/
var Element = {
/*
Metodo: cache
Mette nella cache un elemento DOM (metodo privato)
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
cache: function(id) {
if(!Elements[id]) Elements[id] = document.getElementById(id);
return Elements[id];
},
/*
Metodo: get
Restituisce un elemento DOM basandosi sull'id dello stesso
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
get: function(id) {
return Element.cache(id);
},
/*
Metodo: getStyle
recupera il valore dell stile inline di una proprietà css specificata
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [string] css: la proprietà css
*/
getStyle: function(id, css) {
return Element.cache(id).style[css];
},
/*
Metodo: setStyle
imposta lo stile inline ad una proprietà css specificata
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [string] css: la proprietà css
- [string] value: il valore della proprietà css
*/
setStyle: function(id, css, value) {
Element.cache(id).style[css] = value;
return Element;
},
/*
Metodo: setStyles
imposta lo stile inline ad una moltitudine di proprietà css specificate
Argomenti:
- [string] id : l'id dell'elemento desiderato
- [object] css: le proprietà css da impostare
*/
setStyles: function(id, css) {
for (var p in css) {
Element.cache(id).style[p] = css[p];
}
return Element;
}
}
/*
Function: $
Recupera un elemento DOM basandosi sull'id
Argomenti:
- [string] id : l'id dell'elemento desiderato
*/
var $ = function(id) {
return Element.get(id);
}
/*
Class: Window
Wrapper per l'oggetto window
*/
var Window = {
/*
Metodo: load
imposta l'evento load
*/
load: function(fn) {
window.addEventListener('load', fn, false);
}
}
Ecco infine il pacchetto contenente la libreria pronto per il download.
Conclusione
Come abbiamo potuto osservare nel corso di questa seconda parte, la realizzazione di una libreria è un compito molto impegnativo, che racchiude, oltre alle competenze tecniche, anche una certa eleganza nello sviluppo delle API e soprattutto nell'attenzione alle performance.
Prima di considerare una libreria come stabile, occorrerà apportare tutte le migliorie possibili offerte dall'attuale livello della sua struttura, senza tralasciare l'aspetto fondamentale dei test cross-browser. Gli esempi presentati in questo articolo infatti, come precedentemente dichiarato, non si curano di essere compatibili con i browser di casa Microsoft, mentre in uno scenario pratico sarebbe impensabile tralasciare questo grado di compatibilità. È dunque bene essere consci delle differenze tra browser e fare in modo che il proprio prodotto offra una compatibilità piuttosto ampia: l'esperienza e il grado di qualità faranno il resto.