In questo articolo introdurremo un framework di sviluppo scritto interamente in Javascript e
dedicato alla creazione di applicazioni client side: Javascript MVC.
Il progetto assolve il non facile compito di aggregare e far cooperare in modo fluido ed organico alcune delle migliori librerie Javascript disponibili producendo uno strumento completo e molto potente. Affidandoci ai dettami del learning by doing affronteremo un piccolo progetto insieme in modo da saggiare le caratteristiche del framework.
Javascript MVC è composto da 4 librerie principali:
- jQueryMX: un set di estensioni di jQuery che aiutano a sviluppare applicazioni aderenti al pattern MVC;
- FuncUnit: consente di scrivere ed eseguire test per applicazioni Javascript;
- StealJS: gestisce le dipendenze fra le varie librerie e offre alcune utility per la gestione del nostro codice;
- DocumentJS: un ottimo engine per produrre documentazione da codice dell'applicazione.
Le librerie sono scaricabili all'occorrenza anche singolarmente ma, visto l'obiettivo dell'articolo, procediamo al download di Javascript MVC per intero, dal sito ufficiale. Una volta scaricato l'archivio, decomprimiamolo all'interno della cartella di root
servita dal webserver installato sulla nostra macchina, rinominiamo quanto ottenuto come 'javascript-mvc
', e sinceriamoci che sia presente la versione 6.0 o superiore del Java Runtime Environment.
La prima applicazione con JavaScript MVC: Music Shelf
Grazie a StealJS è possibile beneficiare di alcuni comandi per la generazione del codice; proprio come succede per framework server side come Ruby on Rails possiamo creare la nostra applicazione semplicemente portandoci nella cartella appena creata ed eseguendo:
./js jquery/generate/app musicshelf # per linux e macOS
js.bat jquerygenerateapp musicshelf # per windows
Una nuova cartella dal nome 'musicshelf
' dovrebbe confermare il buon esito dell'operazione, senza indugio puntiamo il browser all'indirizzo:
http://127.0.0.1/javascript-mvc/musicshelf/musicshelf.html
per essere accolti dalla pagina di benvenuto del framework.
Diamo un'occhiata alle cartelle ed ai file che il generatore ha creato per noi:
Campo | Descrizione |
---|---|
musicshelf.js musicshelf.html musicshelf.css |
rappresentano il punto di ingresso dell'applicazione nei suoi tre aspetti, html, css e javascript. Vista la natura del framework il compito di coordinamento spetterà proprio a musicshelf.js |
docs |
lo spazio dedicato alla documentazione di progetto |
fixtures |
contiene dei dati finti utilissimi per testare e lavorare nelle fasi iniziali del progetto: in questo modo possiamo posticipare la creazione di un web service con il quale far dialogare la nostra applicazione simulandone le interazioni attraverso i file contenuti in questa cartella |
scripts |
raccoglie alcuni script di manutenzione che possono essere lanciati per richiedere, ed esempio, la generazione della documentazione di progetto o la pulizia e la validazione automatica del codice sviluppato |
test |
in questa cartella sono racchiusi i file di test sia di tipo funzionale che unitario |
Javascript MVC, caricare l'applicazione
Se analizziamo musicshelf.html
notiamo che l'unica inclusione javascript è la seguente:
<script type='text/javascript' src='../steal/steal.js?musicshelf'></script>
Questa request carica sia la libreria StealJS sia il file musicshelf.js
, responsabile di ogni altra richiesta dell'applicazione:
steal(
'./musicshelf.css', // il css dell'applicazione
'./models/models.js', // carica i modelli (se presenti)
'./fixtures/fixtures.js', // carica i files con i dati fittizi
function(){ // eventuali configurazioni da eseguire
// a caricamento completato
}
)
La predisposizione dell'applicazione interessa anche la parte dei test, ovviamente non ne abbiamo
ancora scritto nessuno ma l'impianto già funziona perfettamente: per verificarlo di persona basta
puntare il browser all'indirizzo:
http://127.0.0.1/javascript-mvc/musicshelf/qunit.html
per avere un report sull'esecuzione dei test unitari e a
http://127.0.0.1/javascript-mvc/musicshelf/funcunit.html
per quelli funzionali.
Operare in modalità MVC
Una libreria musicale non può avere questo nome senza la possibilità di memorizzare ed accedere all'elenco delle canzoni. In una piattaforma MVC come quella in cui ci troviamo, significa sviluppare un model che rappresenti l'oggetto canzone e scrivere delle azioni all'interno di uno o più controller che ci consentano di interagire con esse.
Javascript MVC possiede un generatore di scaffold capace di creare per noi tutto lo scheletro di questo costrutto partendo semplicemente dal nome del model che ci interessa sviluppare. Portiamoci quindi nella cartella 'javascript-mvc' ed eseguiamo questo comando:
./js jquery/generate/scaffold Musicshelf.Models.Song # per linux e macOS
js.bat jquerygeneratescaffold Musicshelf.Models.Song # per windows
Per prima cosa concentriamo la nostra attenzione sul nuovo model 'models/song.js
': sono rappresentati 5 metodi di classe che puntano ad altrettante url json. Ovviamente non abbiamo un webservice remoto che si occupa di gestire l'oggetto song, per cui dovremo accontentarci della sua simulazione attraverso le fixtures; è però importante sapere che sono questi gli url che devono essere calibrati verso il corretto servizio remoto in caso questo sia presente.
Poco più in basso trova spazio la possibilità di aggiungere metodi di istanza, approfittiamone per dichiarare 'prettyPrint
' che ritorna una stringa contenente il nome del brano, quello dell'artista e la durata dell'esecuzione.
/* @Prototype */
{
prettyPrint : function(){
return this.artist + ": " + this.name + " (" + this.time +") ";
}
});
Ora analizziamo la sottocartella 'song
', dove possiamo trovare i file creati per noi dallo scaffold in relazione alle due azioni essenziali per l'interazione con il modello: la creazione di nuovi elementi e la visione di quelli creati.
Per ogni azione il framework mette a disposizione:
- il controller (es: 'create/create.js') che specifica i comportamenti legati ai vari eventi lanciati dell'azione corrente; nel caso di
create.js
questi sono:- '
submit
', chiamato all'invio della form di creazione - '
saved
', invocato al completamento dell'azione di salvataggio - '
init
' che si configura come evento di setup e tipicamente viene utilizzato per mostrare a video la porzione di html legata all'azione corrente, in questo caso il form di inserimento
- '
- il template html (es: '
create/view/init.ejs
') in formato ejs, dove con il tag speciale '<% %>
' è possibile inserire codice javascript. Questa tecnica è assolutamente comune per i framework server side (basti pensare a .NET, Ruby On Rails o NodeJS) ma interessante ed innovativa vista lato client. - i test contestuali al controller (es: create_test.js, create.html, e funcunit.html) già predisposti e funzionanti (url:
http://127.0.0.1/javascript-mvc/musicshelf/song/create/funcunit.html
) ai quali si potranno poi aggiungere i nostri.
Approfittiamo di questo momento per adattare lo scaffold generato alle nostre necessità. In primo luogo modifichiamo il form ('create/view/init.ejs') di creazione specificando i campi necessari al
modello 'song':
<h2>New song</h2>
<p>
<label for='name'>Name</label> <br/>
<input type='input' name='name' id='name'/>
</p>
<p>
<label for='artist'>Artist</label> <br/>
<input type='input' name='artist' id='artist'/>
</p>
<p>
<label for='time'>Time (seconds) </label> <br/>
<input type='number' name='time' id='time'/>
</p>
<p>
<label for='file_url'>File URL:</label> <br/>
<input type='url' name='file_url' id='file_url'/>
</p>
<p><input type='submit' value='Create'/></p>
Ora eseguiamo i test del controller create http://127.0.0.1/javascript-mvc/musicshelf/song/create/funcunit.html
,
e notiamo come ci vengano segnalati alcuni errori, questo deriva dalle modifiche che abbiamo fatto al form e che devono essere propagate anche al test ad essa dedicato: modifichiamo quindi il test ('create/create_test.js') per rispecchiare queste modifiche:
steal('funcunit',function(){
module("Musicshelf.Song.Create", {
setup: function(){
S.open("//musicshelf/song/create/create.html");
}
});
test("create songs", function(){
// simulo il popolamento del form:
S("[name=name]").type("Rudolph the Red-Nosed Reindeer");
S("[name=artist]").type("Ray Charles");
S("[name=time]").type("240");
S("[name=file_url]").type("http://127.0.0.1/javascript-mvc/mp3s/ray_charles.mp3");
S("[type=submit]").click();
S('h3:contains(Rudolph the Red-Nosed Reindeer)')
.exists(function(){
// mi aspetto che i campi della form siano vuoti
ok(true, "Rudolph the Red-Nosed Reindeer added");
equals(S("[name=name]").val() , "", "form reset");
equals(S("[name=artist]").val() , "", "form reset");
equals(S("[name=time]").val() , "", "form reset");
equals(S("[name=file_url]").val() , "", "form reset");
})
});
});
Vediamo ora l'azione 'list' nel file 'list.js'; in questo caso gli eventi per i quali questo controller è
programmato a reagire, oltre al già citato 'init',
sono:
- '.destroy click': il click su di un elemento con classe 'destroy' attiva questa funzione che invoca il
metodo 'destroy' sull'istanza del model associato allo specifico elemento; - '{Musicshelf.Models.Song} destroyed/created/updated': questi 3 eventi sono lanciati dal model a
segnalazione di modifiche su di un'istanza della classe song in modo che il controller possa
prendere le necessarie misure sull'interfaccia; ad esempio la ricezione di un 'destroyed' comporta
la rimozione del corrispondente tag 'li' dalla lista.
appena sotto aggiungiamo la possibilità di riprodurre una delle canzoni in elenco, per
farlo mettiamo un handler in ascolto al click su un elemento con classe 'play':
'.play click': function( el ){
(new Audio(el.closest('.song').model().file_url)).play();
}
Attenzione, per attivare questo comportamento ci siamo avvalsi dell'oggetto 'Audio' assumendo quindi che il browser di destinazione supporti questa nuova specifica HTML5; inoltre anche il formato del file da riprodurre dovrà essere conforme al browser di destinazione. L'esempio proseguirà con l'utilizzo di un file .mp3
, supportato da Chrome, IE e Safari: se si volesse invece riprodurre il tutto su Firefox l'estensione richiesta è .ogg
.
Ovviamente in un ambiente di produzione andrebbero prese adeguate contromisure per saggiare il supporto a queste nuove specifiche W3C (usando ad esempio Modernizr) e per gestire il supporto di formati di encoding multipli, avvalendosi dell'elemento source.
Proseguiamo modificando il template dedicato alla visualizzazione del singolo elemento ('list/views/song.ejs
') per introdurre il pulsante di riproduzione ed utilizzare il metodo 'prettyPrint
' per la stampa:
<p>
<%= prettyPrint() %>
<a href='javascript://' class='destroy'>X</a>
<a href='javascript://' class='play'>P</a>
</p>
questo template verrà incrociato con tutti gli elementi recuperati dal controller in modo da produrre la lista delle canzoni; anche in questo caso dobbiamo adattare il test funzionale
http://127.0.0.1/javascript-mvc/musicshelf/song/list/funcunit.html
in accordo alle modifiche apportate. A differenza del controller precedente però qui i punti di intervento sono due, infatti dovremo modificare anche il file html utilizzato dal test (list/list.html
) in quanto contiene un'invocazione al metodo di creazione di una nuova istanza di Song:
// dalla riga 22 sostituire con:
$('#create').click(function(){
new Musicshelf.Models.Song({
name: "Special Needs",
artist: "Placebo",
time: 180,
file_url: 'http://127.0.0.1/javascript-mvc/mp3s/placebo.mp3'
}).save();
})
Ora insistiamo sul file di test (list/list_test.js
) allineando i controlli con quanto specificato nel template:
steal('funcunit',function(){
module("Musicshelf.Song.List", {
setup: function(){
S.open("//musicshelf/song/list/list.html");
}
});
test("delete songs", function(){
S('#create').click()
// wait until grilled cheese has been added
S('p:contains(Special Needs)').exists();
S.confirm(true);
S('p:last a').click();
S('h3:contains(Special Needs)').missing(function(){
ok(true,"Special Needs Removed")
});
});
});
Ricaricando la pagina di test noteremo come ora sia tutto perfettamente funzionante. Per finire spostiamoci sulle fixtures analizzando il file 'fixtures/fixtures.js
'; come possiamo intuire il codice istruisce il framework nel creare 5 istanze fittizie dell'oggetto Song
ma sbaglia nello specificare i parametri di cui essi sono formati (name e description), modifichiamo quindi in accordo con le nostre necessità:
// map fixtures for this application
steal("jquery/dom/fixture", function(){
$.fixture.make("song", 5, function(i, song){
return {
name: "song " + i,
artist: "artist " + i,
time: Math.round(Math.random() * 200),
file_url: "http://127.0.0.1/javascript-mvc/mp3s/demo_song.mp3"
}
})
})
Ora non resta che aprire 'musicshelf.js
' all'interno di un editor di testo per prendere
coscienza di come l'ultimo generatore abbia aggiunto i due controller 'song/create
' e 'song/list
' all'elenco dei file da includere nell'applicazione e abbia predisposto l'attivazione degli stessi su due elementi HTML con id rispettivamente 'songs' e 'create' che dovremo provvedere a creare all'interno di 'musicshelf.html
'; modifichiamo quindi in questo modo il documento rimuovendo tutto il testo di presentazione di Javascript MVC:
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>musicshelf</title>
</head>
<body>
<h1>Welcome to JavaScriptMVC 3.2!</h1>
<ul id='songs'></ul>
<form id='create' action=''></form>
<script type='text/javascript' src='../steal/steal.js?musicshelf'></script>
</body>
</html>
Testare un'applicazione Javascript MVC
Creiamo la cartella 'mp3s
' e copiamo al suo interno un file con estensione .mp3 ricordandoci di rinominarlo in accordo con quanto inserito in 'fixtures.js' ('demo_song.mp3'). Ora puntiamo il browser all'indirizzo:
http://127.0.0.1/javascript-mvc/musicshelf/musicshelf.html
e sinceriamoci della correttezza e del funzionamento di tutte le interazioni possibili.
Quanto abbiamo visto non è che la punta dell'iceberg delle possibilità offerte da Javascript MVC. Tra i temi di approfondimento spicca il routing
, la possibilità di gestire le associazioni fra diversi modelli e tantissimo altro. La documentazione, piccola ma ben gestita, è sicuramente la fonte più indicata di nuovi spunti e dettagli.
Link Utili
- Javascript MVC sito ufficiale
- Librerie del framework
- Documentazione