Semplice HTML5 Mobile App con Bootstrap Ratchet e Backbone.js
In un precedente articolo avevo parlato di 8 mobile framework per la realizzazione di applicazioni mobile in HTML5, CSS3 e Javascript, in cui ho inserito anche Ratchet, che offre stili CSS3 molto puliti ed un markup in linea con le moderne specifiche HTML5.
Dopo che lo scorso mese nel blog di Twitter Bootstrap è stato annunciato che Ratchet 2.0 farà parte d’ora in poi dell’organizzazione Bootstrap, ho deciso di rilasciare l’esempio di una semplice HTML5 Mobile App sviluppata con Backbone.js usando gli stili (e solo gli stili) di questa nuova versione di Ratchet, la quale quale offre 3 ottimi skin di partenza: quello di base, quello stile iOS, e quello stile Android.
Cosa desideriamo di meglio?
L’intento è quello di realizzare una view iniziale con un header, un sub-header contenente un input di ricerca, ed il contenitore per mostrare una lista di articoli; grazie all’input di ricerca l’utente può filtrare i contenuti della lista.
L’applicazione è quindi composta da 3 backbone view:
- la prima rappresenta la schermata principale contenente header e sub-header
- la seconda rappresenta la view della lista
- la terza rappresenta ogni articolo che dovrà essere inserito nella lista
Come nello scorso esperimento, lavoreremo sempre con model e collection per definire rispettivamente il modello di articolo e la collezione per la lista degli articoli.
Ecco lo schema del funzionamento dei vari componenti che formano la nostra HTML5 Mobile App:
Trovi le DEMO dei 3 diversi stili ed il DOWNLOAD alla fine di questo articolo!
HTML5 mobile app: impostazione del documento
Come prima cosa andiamo a scaricare lo zip di Ratchet dal sito ed includiamo nel nostro documento solo gli stili CSS ed il font per le icone. Per usare lo stile base dovremo includere solo il file ‘ratchet.css‘ o ‘ratchet.min.css‘, mentre per gli stili iOS o android dovremo aggiungere anche i rispettivi file ‘ratchet-theme-ios.css‘ o ‘ratchet-theme-android.css‘.
Ecco l’esempio per l’utilizzo del tema iOS:
1 2 3 4 5 |
<!-- includiamo gli stili di base di Ratchet.css --> <link href="css/ratchet/css/ratchet.css" rel="stylesheet"> <!-- includiamo gli stili iOS di Ratchet.css --> <link href="css/ratchet/css/ratchet-theme-ios.css" rel="stylesheet"> |
Poi inseriamo nel documento le varie librerie javascript, prima della chiusura del tag </body> in modo da velocizzare il caricamento della pagina:
1 2 3 4 5 6 7 |
<!-- Includo gli scripts in fondo alla pagina per velocizzarne il caricamento --> <script src="js/jquery-1.10.2.min.js"></script> <!-- Includiamo anche underscore.js libreria a cui si appoggia backbone.js --> <script src="js/underscore.js"></script> <script src="js/backbone.js"></script> <!-- il file javascript del nostro esperimento--> <script src="js/demo.js"></script> |
Definizione di model e collection
Vediamo come definire il model e la collection deli articoli:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//definiamo il modello di un articolo var ArticleModel = Backbone.Model.extend({ // all'inizializzazione di ogni modello creiamo la relativa anteprima initialize: function(){ var article = this.get("article"); this.set("preview", article.substr(0, 70)+"..."); }, // Attributi predefiniti per il modello, che vengono usati //se non passati al momento dell'inizializzazione defaults: { id: new Date().getUTCMilliseconds(), //impostiamo un id univoco in base alla data title: "Nessun titolo", article: "", image: false, createdat: new Date() } }); //definiamo la collection per la lista degli articoli var ArticleList = Backbone.Collection.extend({ //Impostiamo la referenza al model per questa collection. model: ArticleModel, //funzione che restituisce il numero seriale per l'id di ogni nuovo articolo nextId: function() { if (!this.length) return 1; return this.first().get('test') + 1; }, //la funzione per la ricerca, che restituisce solo i models degli articoli //che contengono il termine ricercato nel titolo (la usaeremo per la ricerca) search : function(term){ if(term == "") return this; var pattern = new RegExp(term,"gi"); return _(this.filter(function(data) { return pattern.test(data.get("title")); })); } }); |
Le view
Ecco come si presenta la view principale:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
//definiamo la view principale della nostra app var StartView = Backbone.View.extend({ // comunichiamo a backbone che deve inserire il markup del template di questa view all'interno di un oggetto dal tag 'section' tagName: "section", //settiamo la classe che deve avere il tag 'section' className: "page-wrapper", //impostaiamo l'id del template da cui generare il markup di questa view template: "#startview-template", initialize: function() { //compiliamo il template in una variabile this.template = _.template($(this.template).html()); //inizializziamo la view della lista degli articoli this.resultsView = new ListView({collection: this.collection}); }, //funzione che genera il markup di questa view render: function () { this.$el.html(this.template()); //genero l'html di questa view //inserisco l'html della view dei risultati ti ricerca nella div con classe '.content' presente in questa view this.$('.content').append(this.resultsView.render().el); return this; }, // Deleghiamo gli eventi per la ricerca sull'input di testo events: { //evento //selettore //funzione da eseguire "keyup .search-term": "search", "keypress .search-term": "onkeypress" }, //funzione che richiama il methodo 'renderList' della view della lista ad ogni keyup sull'input di ricerca search: function (e) { var term = $(e.currentTarget).val(); //se il termine non è vuoto allora richiamo il 'renderList' passando i dati filtrati, //altrimenti richiamo il render per mostrare la lista intera if(term!="") this.resultsView.renderList(this.collection.search(term)); else this.resultsView.render(); }, onkeypress: function (e) { if (e.keyCode === 13) { // preveniamo il normale evento di invio con il pulsante 'Enter' e.preventDefault(); } } }); |
Avremmo potuto anche “unificare” entrambe le view (quella principale e quella del contenitore della lista) in un unica view, ma lasciandole separate riusciamo ad ottenere un progetto più scalabile e dinamico: potremmo per esempio inserire nell’elemento ‘.content‘ della view principale qualsiasi altra view, oltre a quella della lista. Ciò ci permetterebbe di usare la view principale per più di uno scopo.
Ora vediamo la view del contenitore della lista, che sarà applicata appunto all’interno dell’elemento DOM con classe ‘.content‘ della view principale:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
//view per la lista degli articoli (viene inizializzata all'interno della view 'StartView') var ListView = Backbone.View.extend({ // comunichiamo a backbone che deve inserire il markup del template di questa view all'interno di un 'ul' tagName:'ul', //questa view non ha bisogno di template, perchè viene popolata con le varie view 'ListItemView' //relative ad ogni model presente nella collection //impostiamo la classe che deve avere il contenitore di questa view className:'table-view', initialize:function () { }, //funzione per mostrare la lista completa all'avvio dell'ìapplicazione render : function(articles){ var _this = this; this.$el.empty(); //per ogni elemento della collection creiamo una nuova view this.collection.each(function(task){ var view = new ListItemView({ model: task }); _this.$el.append(view.render().el); }); return this; }, //funzione che viene richiamata al momento della ricerca, alla quale vengono passati i model che //soddisfano la ricerca renderList : function(articles){ var _this = this; this.$el.empty(); //anche qui per ogni model creo la relativa view articles.each(function(task){ var view = new ListItemView({ model: task }); _this.$el.append(view.render().el); }); return this; } }); |
Questa view si preoccupa di mostrare inizialmente tutti i model (quindi gli articoli) presenti all’interno della collection, mentre tramite il metodo ‘renderList‘ mostra solamente la lista di articoli che viene passata come parametro. Tale metodo viene richiamato dalla view principale al momento in cui si verifica l’evento ‘keyup‘ sull’input di ricerca.
Per mostrare i risultati al suo interno, la view della lista provvede alla creazione di una nuova view ‘ListItemView‘ per ogni model degli articoli, sia all’interno del metodo ‘render‘ che del metodo ‘renderList‘:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//view per ogni elemento da inserire nella lista dei risultati degli articoli (viene inizializzata all'interno della view 'ListView') var ListItemView = Backbone.View.extend({ //il tag da usare per il contenitore del markup tagName:"li", //la classe da associare al contenitore del markup className: "table-view-cell", //impostaiamo l'id del template da cui generare il markup di questa view template: "#listitemview-template", initialize:function () { //compiliamo il template in una variabile this.template = _.template($(this.template).html()); //se il model associato subisce delle modifiche allora richiamiamo il render per aggiornare il markup //(non usato in questa demo) this.model.on("change", this.render, this); //se il model associato viene eliminato, allora richiamiamo la funzione 'close' che ne rimuove il //relativo DOM dal documento (non usato in questa demo) this.model.on("destroy", this.close, this); }, //genriamo il markup passando al template compilato i dai del model dell'articolo in formato JSON render:function () { this.$el.html(this.template(this.model.toJSON())); return this; }, close: function(){ //elimina il DOM di questa view dal documento ed annulla gli eventi applicati nella funzione 'initialize' this.remove(); } }); |
I template e la generazione del markup
Come al solito utilizziamo il sistema di templating di underscore.js per la generazione del markup di ogni view, considerando che la view della lista non ha bisogno di un template, perché come abbiamo visto il markup da inserire al suo interno è costituito dalle varie view degli articoli.
Per avere maggiori informazioni sui sistemi di Javascript templating, ottimi per progetti di tipo “Single-Page-Application“, ti consiglio di leggere questo mio precedente articolo:
Vediamo quindi i due template da inserire all’interno del documento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!-- template per la view di ogni elemento all'interno della lista dei risultati di ricerca (ListItemView su demo.js) --> <script type="text/template" id="listitemview-template"> <a href="#" data-id="<%= id %>" class="push-right"> <span class="badge"> <%=createdat.getDate()%> / <%=createdat.getMonth()+1%> </span> <%if(image){%> <img class="media-object pull-left" src="images/<%=image%>" /> <%}%> <div class="media-body"> <%= title %> <p><%= preview %></p> </div> </a> </script> <!-- template per la view del contenuto iniziale (StartView su demo.js) --> <script type="text/template" id="startview-template"> <header class="bar bar-nav"> <button class="btn btn-link btn-nav pull-left" onclick="window.location.href='http://www.upcreative.net/iscriviti-alla-newsletter/'"> Altre guide </button> <h1 class="title">Ultimi Articoli</h1> <button class="btn btn-link btn-nav pull-right" onclick="window.location.href='http://www.upCreative.net'"> upCreative.net </button> </header> <div class="bar bar-standard bar-header-secondary"> <input class="search-term" placeholder="Cerca articoli" type="text"/> </div> <div class="content"></div> </script> |
Avviamo l’applicazione
Una volta impostati tutti i componenti necessari, passiamo all’avvio dell’applicazione, inizializzando una nuova collection per gli articoli con dei dati iniziali:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
$(function(){ //creiamo una nuova istanza per la collection degli articoli con 4 models iniziali var articles = new ArticleList([ { title: "App su upCreative.net", image: "1.jpg", article: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }, { title: "Secondo articolo su upCreative.net", image: "2.jpg", article: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur" }, { title: "Articolo senza immagine 3", image: "3.jpg", article: "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" }, { title: "Quarto articolo sulla lista", image: "4.jpg", article: "Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" }, { title: "Quinto articolo sulla lista", image: "5.jpg", article: "Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" }, { title: "Sesto articolo sulla lista", image: "1.jpg", article: "Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" }, { title: "Settimo articolo sulla lista", image: "2.jpg", article: "Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat" } ]); //creiamo una nuova istanza per la view iniziale //associandogli la collection appena creata e popolata var startView = new StartView({ collection : articles }); $("body").append(startView.render().el); }); |
Backbone.js e Ratchet HTML5 mobile app: il risultato
Vediamo il risultato live nei 3 skin differenti offerti da Ratchet:
Stile di base
Stile di iOS
Stile di Android
Crea la tua mobile app in HTML, CSS3 e Javascript
Utilizzando questa tecnica insieme a molte altre, ho realizzato più di 10 mobile app in HTML, CSS3 e Javascript (tra cui Tint e Dieta SI o NO?).
Se anche tu vuoi realizzare la tua applicazione in HTML, CSS e javascript da distribuire nei vari store, forse potrebbe interessarti il mio e-book HTML Mobile Accelerato.
Giacomo Freddi
Web Designer Freelance e Developer, si occupa del design e dello sviluppo di applicazioni web dal 2008, come molti freelance è abituato a gestire più ruoli e spaziare su più campi, ma la sua passione principale è quella della creazione di interfacce front-end e back-end utilizzando codice html5 e css3. Adora usare pattern MVC per i suoi Javascript.
Pingback: Semplice HTML5 mobile app con Backbone.js e Rat...()
Pingback: Creare Mobile App in HTML e Web App: benefici e svantaggi di Backbone()
Pingback: Come creare un app: realizzare una view di login con il localStorage | upCreative()