Single page web applications! Backbone.js è una libreria Javascript che permette di semplificare questo pattern di sviluppo. Più in generale, Backbone.js contribuisce all’adozione di un metodo di design delle applicazioni javascript, sfruttando il modello MVP (Model View Presenters), una variante logica del classico Model-View-Controller.
Lo scopo di questo articolo è fornire le basi di utilizzo della libreria, analizzando un esempio pratico. Si vuole visualizzare all’interno di una pagina web l’elenco dei progetti che un ipotetico team di sviluppo ha completato nell’ultimo mese. I requisiti essenziali per proseguire nella lettura sono la conoscenza di:
- jQuery (1.4 >)
- Rails (2.3.x >)
- HTML e Javascript
1. La struttura
Il primo passaggio è generare tutti i file che ci servono, per avere un controller, un model e una view con cui lavorare. Dalla console scriviamo:
script/generate scaffold Project title:string description:text rake db:migrate
Avremo la struttura database funzionante per procedere.
2. Le dipendenze
Ci sono alcune dipendenze da rispettare perchè Backbone.js possa funzionare correttamente. In particolare:
- Underscore.js ( > 1.3.1) per tutte le operazioni di selezione dati, basic templating, etc
- json2.js per dinamiche di RESTful persistence, history support con Backbone.Router e DOM manipulation con Backbone.View
- Framework jQuery ( > 1.4.2) o Zepto.
Lo scaffold precedentemente eseguito ha generato un layout projects.html.erb, all’interno del quale dobbiamo inserire le opportune librerie javascript necessarie:
<%= javascript_include_tag 'jquery-1.8.2.min', 'underscore', 'backbone', 'json2' %> <%= javascript_include_tag 'application' %>
3. Lato server
Sempre lo scaffold ha generato un ProjectsController che contiene il metodo index così definito:
# GET /projects
# GET /projects.xml
def index
@projects = Project.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @projects }
end
end
La nostra intenzione è quella di leggere dal database la lista dei progetti effettuando una richiesta tramite Backbone.js, e non direttamente dal controller prima del caricamento della View. Pertanto, il nuovo metodo index dovrà essere modificato in questo modo:
# GET /projects
# GET /projects.js
# GET /projects.xml
def index
respond_to do |format|
format.html # index.html.erb
format.js {
@projects = Project.all
render :json => @projects.to_json
}
format.xml { render :xml => @projects }
end
end
Nessuna query verrà eseguita, finchè non arriva una richiesta GET sul path /projects.js
4. Parte HTML
Anche il file projects/index.html.erb è stato automaticamente costruito in questo modo dallo scaffold:
<table>
<tr>
<th>Title</th>
<th>Description</th>
</tr>
<% @projects.each do |project| %>
<tr>
<td><%=h project.title %></td>
<td><%=h project.description %></td>
<td><%= link_to 'Show', project %></td>
<td><%= link_to 'Edit', edit_project_path(project) %></td>
<td><%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
Il problema è che, al caricamento della pagina, la collezione @projects non esiste più. L’abbiamo rimossa dal metodo index. Il nuovo file projects/index.html.erb si dovrebbe presentare così:
<table class="projects-wrapper">
<tr>
<th>Title</th>
<th>Description</th>
</tr>
</table>
Notare la proprietà class=”projects-wrapper” aggiunta alla tabella. Ci tornerà utile in seguito.
5. Template javascript
A questo punto, dobbiamo aggiungere al file HTML della index anche una porzione di codice (il nostro template javascript) che rappresenterà ogni nuova riga (ogni Progetto) che verrà aggiunta alla tabella, in modo dinamico, da Backbone.js
Quindi, in fondo alla pagina, scriviamo:
<script type="text/template" id="ProjectLineTpl">
<td>{{=title}}</td>
<td>{{=description}}</td>
</script>
6. Finalmente Backbone.js
Questo passaggio è il più importante. Costruiamo il file application.js che conterrà tutta la logica di Backbone.js. Finalmente! Lets dive into it…
Buone norme di programmazione suggeriscono di separare i Model, dalle View e da altre configurazioni software. Per ragioni di spazio in questo esempio semplifichiamo le cose. Per approfondire la questione, fai riferimento a qualsiasi framework di Asynchronous Script Loading.
Backbone.js permette di gestire 3 elementi fondamentali:
- I Model (rappresentano l’entità da visualizzare. Es: un progetto)
- Le Collection (rappresentano l’insieme dei Model. Es: un insieme di progetti)
- Le View (rappresentano le porzioni di DOM dell’intera applicazione)
Nel nostro caso dobbiamo definire:
Il Modello
var Project = Backbone.Model.extend({});
La Collezione
E’ necessario indice qual è il Model gestito dalla collezione e l’URL di riferimento per tutte le operazioni di fetch (recupero) o save (inserimento / aggiornamento) della collazione.
var Projects = Backbone.Collection.extend({
model: Project,
url: '/projects'
});
La ProjectView
Gestisce la porzione HTML di ogni singola istanza del Model (quindi ogni progetto inserito a database, che verrà recuperato per essere visualizzato a schermo)
var ProjectView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#ProjectLineTpl').html()),
render: function(){
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
La ProjectsView
Viene utilizzata come wrapper per recuperare il codice HTML dell’intera collezione che è stata costruita, inserirlo sulla posizione finale (la nostra tabella) ed eventualmente proseguire con altri controlli (gestione degli eventi, etc)
var ProjectsView = Backbone.View.extend({
el: $('.projects-wrapper'),
initialize: function(){
_.bindAll(this, 'render');
projects.on('reset', this.render);
},
render: function(){
var self = this;
projects.each(function( project ){
var p = new ProjectView({
model: project
});
self.$el.append(p.render().$el);
});
return this;
}
});
Al termine non rimane che invocare il tutto con le seguenti istruzioni:
// Chiamata SINCRONA all'URL specificato nella collection, per recuperare i dati (in formato JSON)
jQuery.ajaxSetup({ async: false });
projects.fetch();
jQuery.ajaxSetup({ async: true });
// Launch start
var App = new ProjectsView();
App.render();
Il vero “cuore” pulsante di questa piccola applicazione Backbone.js è rappresentato dall’ultima View (la ProjectsView) perchè innesca l’intero meccanismo di:
- scansione della Collection
- creazione delle istanze ProjectView: viene passato 1 parametro che è un oggetto contenente il singolo Project da visualizzare
- Costruzione dell’HTML del singolo Project (la singola riga della tabella)
- Aggiunta dell’HTML sopra citato in coda alla tabella
Attenzione! Non esiste un metodo assoluto per raggiungere il risultato che abbiamo appena visto. La logica adottata può essere cambiata e sostituita a piacimento, sfruttando le potenzialità di questa libreria.
Il risultato è una singola pagina web che si occupa, attraverso un po’ di codice Javascript, di leggere dal server e visualizzare una collezione di dati. Pensiamo agli ambiti di utilizzo più complessi: grandi quantitativi di dati che possono essere recuperati un po’ alla volta (la pagina viene costruita in dinamico), inserimento live da parte dell’utente di nuove informazioni, e molto altro.
Annotazione: Rails è basato su un templating engine chiamato ERB che utilizza, di default, la stessa sintassi di underscore.js per l’output delle variabili (<%= variabile %>). Per usare Backbone.js negli ambienti Rails configurati con ERB evitando conflitti, è necessario inserire questo codice (modificabile a piacimento):
_.templateSettings = {
interpolate: /{{=(.+?)}}/g,
evaluate: /{{(.+?)}}/g
};






L’argomento é interessante, ma personalmente ho ancora dei punti aperti: fare le view una volta sola, gestire l’autenticazione in sicurezza,.. Spine.js mi piace un po’ di più comunque la parte actionview di rails diventa superata in questi tipo di applicazioni
Certo, su applicazioni di questo tipo la libreria ActionView di Rails è inutile. Non ho capito cosa intendi con “fare le view una volta solo”. Per il resto non ho provato Spine.js e un modo per autenticare in sicurezza le chiamate è predisporre una whitelist sui domini abilitati.