Every month, you’ll find free technical tutorials in Appliness about web application developement. If you have to suggest ideas, if you want to contribute writing articles, feel free to contact us using the feedback form !
In this introductory tutorial, I will cover BackboneJS, jQuery Mobile and RequireJS, to help web developers to build a modular mobile application. When using these technologies , it will be very easy for you to package and deploy the application to multiple platforms.
You will build a simple sample application step by step, learning how to:
- Modularize the application using BackboneJS,jQuery Mobile and RequireJS;
- Use Backbone View, Collection/Model and Routers.
- Decouple the mobile view and model/collection using Backbone events.
SAMPLE APPLICATION
The demo application has two views:Home view and List view. The Home view features a list of categories for IT books, and the List View actually lists the books available in the store.
JavaScript library and CSS
This tutorial will help you start combining jQuery mobile , Backbone.JS and RequireJS to build an mobile application with separation of concerns. The JavaScript libraries you will use are listed as below:
- jQuery: http://jQuery.com/
Require and plugin:
- require.js: http://requirejs.org/
- require plugin text.js:http://requirejs.org/docs/download.html#text
Backbone and Underscore:
UnderscoreJS removed AMD – Asynchronous Module Definition (require.js) support with version 1.3.0. And Backbone.js was affected as well. Fortunately, James Burke maintains an AMD compatible version of unders coreJS and backboneJS. That is why the download links of underscore and backbone below are not pointed to the official sites.
- amd-underscore: https://github.com/amdjs/underscore
- amd-backbone: https://github.com/amdjs/backbone
- jQuery Mobile: http://jQuerymobile.com/
Project structure
In a jQuery Mobile application, each ‘view’ on the mobile device is a ‘page’ and is declared with an element using the data-role=‘page’ attribute. So in this tutorial, the term ‘view’ refers to a ‘page’ of jQuery Mobile. Multiple ‘pages’ could be organized inside the ‘body’ of an HTML page and jQuery Mobile will manage them automatically, just like the ‘multi-page template structure’ described in jQuery Mobile documentation.
Of course, loose-coupled architecture is better. Using RequireJS and Backbone.JS, you can divide the ‘pages’ into separate files as View modules with templates. This allows modular development and testing. It is also easier to extract the model and logic of the view into behavior objects.
Let’s start building this modular application step by step.
index.html
index.html is just a shell page.
<!DOCTYPE html>
<html>
<head>
<title>DEMO APPLICATION</title>
<meta name=”viewport” content=”width=device-width, initial-scale=1”/>
<meta http-equiv=”Access-Control-Allow-Origin” content=”*”/>
<link rel=”stylesheet” href=”css/jquery.mobile-1.1.0.css” />
<link rel=”stylesheet” href=”css/style.css” />
<!-- we will use cordova to package the mobile application-->
<script src=”js/vendor/phoneGap/cordova-1.6.0.js”></script>
<!-- require.js: data-main attribute tells require.js to load
js/main.js after require.js loads. -->
<script data-main=”js/main” src=”js/vendor/require/require.js”></script>
</head>
<body>
</body>
</html>
Once Require.JS is loaded, it will take the value of the data-main attribute and make a Require call. In main.js, you usually configure path settings for RequireJS.
entry point: js/main.js
In main.js, you configure Require and jQuery Mobile and then bootstrap the application.
require.config({
//path mappings for module names not found directly under baseUrl
paths: {
jquery: ‘vendor/jqm/jquery_1.7_min’,
jqm: ‘vendor/jqm/jquery.mobile-1.1.0’,
underscore: ‘vendor/underscore/underscore_amd’,
backbone: ‘vendor/backbone/backbone_amd’,
text: ‘vendor/require/text’,
plugin: ‘plugin’,
templates: ‘../templates’,
modules: ‘../modules’,
model: ‘../model’
}
});
//1. load app.js,
//2. configure jquery mobile to prevent default JQM ajax navigation
//3. bootstrapping application
define([‘app’,’jqm-config’], function(app) {
$(document).ready(function() {
console.log(“DOM IS READY”);// Handler for .ready() called.
});
app.initialize();
});
RequireJS will load and evaluate app.js and jqm.config.js first as the the dependencies of the module. App.js is mapped to app so that you can call app.initialize() to bootstrap the Backbone application.
Disable jQuery Mobile AJAX navigation system: jqm-config.js
There are routing conflicts between jQuery mobile and Backbone.js but there are several ways to workaround it. I like the magic from Christophe Coenraets (Using Backbone.js with jQuery Mobile: http://coenraets.org/blog/2012/03/using-backbone-js-with-jQuery-mobile/) .
In jqm-config.js, disable the default jQuery Ajax navigation system. Then, use the Backbone router to control the application and manually call changePage() function to switch between the views. You also remove the hidden page from DOM so there is only one view in the DOM every time.
define([‘jquery’], function($){
‘use strict’;
$(document).bind(“mobileinit”, function () {
$.mobile.ajaxEnabled = false;
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
// Remove page from DOM when it’s being replaced
$(‘div[data-role=”page”]’).live(‘pagehide’, function (event, ui) {
$(event.currentTarget).remove();
});
});
});
Setup application router: router.js
You use the Backbone router as the nav system of the application. Every time the user clicks a link, jQuery mobile will change the hash segment which will trigger Backbone to navigate user to the right view via the Backbone router.
Here is router.js. The root url (‘’) is mapped to showHome() function, the same with “/#home” hash.
define([‘jquery’, ‘underscore’, ‘backbone’,’modules/home/home’,
‘model/book/bookCollection’,
‘modules/list/books’,
‘jqm’],
function($, _, Backbone,HomeView,BookCollection,BookListView) {
‘use strict’;
var Router = Backbone.Router.extend({
//define routes and mapping route to the function
routes: {
‘’: ‘showHome’, //home view
‘home’: ‘showHome’, //home view as well
‘list/:categoryId’ : ‘showBooks’,
‘*actions’: ‘defaultAction’ //default action
},
defaultAction: function(actions){
this.showHome();
},
showHome:function(actions){
// will render home view and navigate to homeView
},
});
return Router;
});
Bootstrapping application : app.js
app.js will create backbone router object and then expose the function to allow bootstrap backbone application.
define([‘jquery’,’underscore’, ‘backbone’,’router’],function
($, _, Backbone,Router) {
‘use strict’;
var init=function(){
//create backbone router
var router=new Router();
Backbone.history.start();
};
return{
initialize:init
}
});
Home View
Now, you are ready to render the first view : home view.
1. Define a module for home view: home.js
To render homeView using a template, create an object by extending Backbone.View. Here is the homeView code: js/modules/home/home.js :
define([‘jquery’, ‘underscore’, ‘backbone’,
’text!modules/home/homeViewTemplate.html’],
function($, _, Backbone, homeViewTemplate){
var HomeView = Backbone.View.extend({
//initialize template
template:_.template(homeViewTemplate),
//render the content into div of view
render: function(){
//this.el is the root element of Backbone.View. By default, it is a div.
//$el is cached jQuery object for the view’s element.
//append the compiled template into view div container
this.$el.append(this.template());
//return to enable chained calls
return this;
}
});
return HomeView;
});
text.js, a RequireJS plugin, can help you load the text-based template file through the ‘text!’ prefix so you can separate the template from script file. ‘modules/home/homeViewTemplate.html’ will be loaded automatically and then passed to the module function as the argument “homeViewTemplate”.
Inside the module function, we use the template engine of Underscore.js to compile the template, and then append the resulting HTML segment into the view’s container: this.el, which is a div by default. So you have rendered the view but have not inserted it into DOM.
2. Define the template for home view: homeViewTemplate.html
The template for homeView is a static page: modules/home/homeViewTemplate.html
<div data-role=”content” >
<div class=”content-primary”>
<p class=”intro”>
<strong>Welcome.</strong> It is a simple demo to show how to
build mobile application using JQuery Mobile, Backbone.js and Require.js .
</p>
<ul data-role=”listview” data-inset=”true” >
<li data-role=”list-divider” class=”listTitle”>IT BOOKSTORE</li>
<li data-theme=”a”><a href=”#list/1”>JavaScript</a></li>
<li data-theme=”a”><a href=”#list/2”>NodeJS</a></li>
<li data-theme=”a”><a href=”#list/3”>IOS</a></li>
</ul>
</div><!-- /content -->
</div>
As you can see, in our example application, the template of HomeView has no “Header” and “Footer”. All content is placed into a content div with ‘data-role=”content”’ specified. jQuery Mobile uses HTML5 data- attributes to allow for markup-based initialization and configuration of widgets.
Inside the content container, add a listview with using ‘data-role=”listview” ’. Each item has a hardcoded link that will change the hash segment of the URL. For example, “#list/1” , 1 is the categoryId, and you will use it to fetch book data from a matching json file later. You will add the mapping in the routes of router.js later to allow Backbone to invoke mapping functions in response to the user’s interaction.
3. ShowHome in router.js
Use showHome() to insert the view into DOM and present HomeView.The updated router.js is as below:
define([‘jquery’, ‘underscore’, ‘backbone’,’modules/home/home’,
‘model/book/bookCollection’,
‘modules/list/books’,
‘jqm’],
function($, _, Backbone,HomeView,BookCollection,BookListView) {
‘use strict’;
var Router = Backbone.Router.extend({
//define routes and mapping route to the function
routes: {
‘’: ‘showHome’, //home view
‘home’: ‘showHome’, //home view as well
‘list/:categoryId’ : ‘showBooks’,
‘*actions’: ‘defaultAction’ //default action
defaultAction: function(actions){
this.showHome();
},
showHome:function(actions){
// will render home view and navigate to homeView
var homeView=new HomeView();
homeView.render();
this.changePage(homeView);
},
init:true,
showBooks:function(categoryId){
//create a collection
var bookList=new BookCollection();
//create book list view and pass bookList as the collection
var bookListView=new BookListView({collection:bookList});
//need to pass this as context
bookListView.bind(‘renderCompleted:Books’,this.changePage,this);
//update view
bookListView.update(categoryId);
},
changePage:function (view) {
//add the attribute ‘data-role=”page” ‘ for each view’s div
view.$el.attr(‘data-role’, ‘page’);
//append to dom
$(‘body’).append(view.$el);
if(!this.init){
$.mobile.changePage($(view.el), {changeHash:false});
}else{
this.init = false;
}
}
});
return Router;
});
First, add one more dependency for router.js: ‘modules/home/home’, which is a module defined in home.js; and then pass it to router module function as argument “HomeView”.
In the showHome function, create the homeView object and render the view content, then pass homeView to the changePage function. The changePage(view) is responsible for setting the jQuery Mobile data-role attribute of the view’s root element (view.$el) and appending it into DOM.
Now, you have an HTML document containing one jQuery mobile ‘page’ div. This is the first ‘page’ of your demo application. jQuery Mobile will find and enhance the pages in the DOM and transition to the first page automatically once the DOM is ready. So it is not necessary to call jQuery mobile $.mobile.changePage() manually for the initial page.
4. Run
Open the browser and run the application at “http://localhost/” (assuming you deployed your application on the root of your web server) , you will see the first view of the application:
List view
When the user clicks any item in the list, the application will navigate to the second view: the book list view, to show the books of the selected category.
1. Prepare json data
To make this demo application as simple as possible, you can emulate a back-end service using local JSON data. For the list view, you need three local json data files mapping to the category items: JavaScript, NodeJS and iOS. The name of the JSON file should follow the format of ‘category’ + id + ‘.json’, like ‘data/category1.json’, ‘data/category2.json’ and ‘data/category3.json’.
For example , “category1.json” looks like as following :
[
{
“id”: “1001”,
“name”: “JavaScript & jQuery: The Missing Manual “
},
{
“id”: “1002”,
“name”: “JavaScript: The Definitive Guide”
},
{
“id”: “1003”,
“name”: “JavaScript: the best parts”
},
{
“id”: “1004”,
“name”: “JavaScript: The Good Parts”
},
{
“id”: “1005”,
“name”: “JavaScript Patterns”
},
{
“id”: “1006”,
“name”: “Head First JavaScript”
}
]
2. Define a module for the model (bookModel.js) and collection (bookCollection.js) Before you get into the list view, you need to define the model of book and the collection.
The book model is very simple. You just extend Backbone.Model and define the default value for the attributes.
define(function(){
var Book=Backbone.Model.extend({
//default attributes
defaults:{
id:””,
name:’’,
category:’’
}
});
return Book;
});
Using Book model, we define the collection of book: bookCollection.js
define([‘jquery’, ‘underscore’, ‘backbone’,’model/book/bookModel’],
function ($, _, Backbone,Book){
var Books=Backbone.Collection.extend(
// Book is the model of the collection
model:Book,
//fetch data from books.json using Ajax
//and then dispatch customized event “fetchCompleted:Books”
fetch:function(categoryId){
var self=this;
var tmpItem;
//fetch the data using ajax
var jqxhr = $.getJSON(“data/category” + categoryId+”.json”)
.success(function(data, status, xhr) {
$.each(data, function(i,item){
//create book for each item and then insert into the collection
tmpItem=new Book({id:item.id,category:categoryId,name:item.name});
self.add(tmpItem);
});
//dispatch customized event
self.trigger(“fetchCompleted:Books”);
})
.error(function() { alert(“error”); })
.complete(function() {
console.log(“fetch complete + “ + this);
});
}
});
return Books;
});
Add a dependency for BookCollection. BookModel is passed to the module function as the argument ‘Book’.The item of the collection is Book. So we set collection’s attribute ‘model’ as Book.
For bookColllection, we will add a function “fetch” to read the json file and populate the collection. Once you get the book list successfully, you will trigger a customized event : ‘fetchCompleted:Books’. Later, you will bind the event listener on this event in the book list view.
3. Create dynamic template for list view: bookViewTemplate.html
The Book list view is also rendered with the template. However, it is a dynamic template and is different than homeView’s template.
Use <%…%> to add script and then the Underscore template can execute arbitrary JavaScript code inside <% … %>. In the following template, the variable ‘data’ will be passed from template function. Of course, you can use any variable name you prefer.
<div data-role=”header” data-position=”fixed”>
<h1>Books</h1>
<a href=”#home” data-icon=”home” data-iconpos=”notext”
data-direction=”reverse”>Home</a>
</div>
<div data-role=”content”>
<ul data-role=”listview” data-inset=”true” >
<!-- data is passed from template engine,
and templat engine will execute the scripts inside <% %>
-->
<% for (var i = 0; i < data.length; i++) { %>
<% var item = data[i]; %>
<li>
<a href=”#detail/<%=item.name%>/<%= item.id%>”>
<%= item.name %></a>
</li>
<% } %>
</ul>
</div>
4. Define a module for list view: book.js
You have already prepared the model, collection and template. Now it’s time to make a view: book.js
define([‘jquery’, ‘underscore’, ‘backbone’,
‘text!modules/list/bookViewTemplate.html’],
function ($, _, Backbone, bookViewTemplate) {
‘use strict’;
var BookListView = Backbone.View.extend({
template: _.template(bookViewTemplate),
update:function(categoryId){
//set callback of the event “fetchCompleted:Books”
this.collection.bind(‘fetchCompleted:Books’,this.render,this);
this.collection.fetch(categoryId);
},
render: function(){
this.$el.empty();
//compile template using the data fetched by collection
this.$el.append(this.template({data:this.collection.toJSON()}));
this.trigger(“renderCompleted:Books”,this);
return this;
}
});
return BookListView;
});
First, add the text dependency “bookViewTemplate” .
BookListView has two functions: update(categoryId) and render() .
The update(categoryId) will call collection’s fetch function to get the book list of the selected category by categoryId. Before that, you need to bind the render() function as an eventListener for the event “fetchCompleted:Books”. Once you get the data successfully, you will render the view with using the template.
In the render function, we use Underscore template engine to compile the template and the data to produce the html segments and then insert into view’s div container. We also trigger the event “renderCompleted:Books” to notify router that the view is ready and please change page.
In the real world, it is better to cache the template and the view to improve performance.
5. Add routes mapping in router.js
Now back to router.js. You will tell router.js how to “route” the application once the user clicks a category item of HomeView.
The same with other modules, we need to add dependencies first.
routes: {
‘’: ‘showHome’, //home view
‘home’: ‘showHome’, //home view as well
‘list/:categoryId’ : ‘showBooks’,
‘*actions’: ‘defaultAction’ //default action
},
Now, once the user clicks the item and changes the hash segment of the URL, Backbone will call showBooks() and pass the category id.
The following code is in router.js.
init:true,
showBooks:function(categoryId){
//create a collection
var bookList=new BookCollection();
//create book list view and pass bookList as the collection
var bookListView=new BookListView({collection:bookList});
//need to pass this as context
bookListView.bind(‘renderCompleted:Books’,this.changePage,this);
//update view
bookListView.update(categoryId);
},
//4. argument ‘view’ is passed from event trigger
changePage:function (view) {
//add the attribute ‘data-role=”page” ‘ for each view’s div
view.$el.attr(‘data-role’, ‘page’);
//append to dom
$(‘body’).append(view.$el);
if(!this.init){
$.mobile.changePage($(view.el), {changeHash:false});
}else{
this.init = false;
}
}
As the codes shows, the attribute “init” is a flag attribute. Like we mentioned before, for the initial page of the jQuery Mobile application, jQuery mobile will enhance and present it automatically. So you should not call $.mobile.changePage() manually.
In the function showBooks() , you create the BookCollection and bind it with BookListView. Before updating the view (which will call bookCollection’s fetch function) , you bind the changePage() function with the event ‘renderCompleted:Books’. So when the view is rendered , you will call changePage() to insert it into DOM and then enhance page and transit to the new page.
In the function changePage(), if it is not the initial page, you manually call the jQuery Mobile function $.mobile.changePage() to load the new page and apply the transition effect.
CONCLUSION
This is just a very basic application and a long way from a real one. Actually, there are lots of things you can improve, like caching the template and view, or even separating the control logic from the router.
Still RequireJS+BackboneJS+jQuery Mobile is a powerful combination and an easy to use technology. In particular, with RequireJS and BackboneJS, you can modularize your application development and make your application code clean and clear.
In this introductory tutorial, I’ve only scratched the surface of jQuery Mobile, BackboneJS and RequireJS. To learn more, visit the official sites of these libraries and frameworks.
Thank you for taking your time to read this tutorial!
Download the source code of this project:
http://www.appliness.com/wp-content/uploads/2012/06/books.zip
Written by Mark Dong
Mark Dong has worked at Adobe for six years in China. Flex, JavaScript and Java developer with much experience on enterprise RIA architect and project manager in FSI and Energy industry. Mark is also the author of “The road of Flex master”.
Article original : ici




