Blog of Appliness

Getting started with HTML mobile application development using jQuery Mobile, RequireJS and BackboneJS

croissant

This tutorial is featured in the free June issue of Appliness.

Download it on your tablet (iPad or Android).

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:

Require and plugin:

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.

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”.

Comments

comments

Powered by Facebook Comments

10 Comments

  1. Miguel
    September 19, 2012

    Hello!
    This article helped very much, thanks.

    I have a problem though…
    While everything works in my browser and my Xcode simulator, “DOM IS READY” never reaches the console when building the app in my iPad!
    So I only see the crude html of the index page and no library is called.
    I have no idea why is that… (document is never ready?)

    define(['app'], function(app) {
    $(document).ready(function() {
    console.log(“DOM IS READY”);// Handler for .ready() called.
    });
    app.initialize();
    });

    Reply
  2. Mel
    September 25, 2012

    I would love to see an extension to this tutorial for displaying specific detail for each book on its own “page”. :)

    Reply
  3. fangzx
    October 5, 2012

    @Miguel, there is a error in the source code:
    define(['app'], function(app) {
    should be:
    require(['app'], function(app) {

    Reply
  4. fangzx
    October 5, 2012

    @Mark Dong, thank you for this great tutorial! I test the application and found that the first page did not display automatically ! I have to change the source code like this:

    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{
    setTimeout(function(){
    $.mobile.changePage($(view.el), {changeHash:false});
    },100) //use setTimeout to avoid “Cannot call method ‘trigger’ of undefined” error!
    this.init = false;
    }
    }

    Reply
  5. Harshad
    October 28, 2012

    Hi
    the source is not working in browser …pls help how torun sourcefile

    Reply
  6. Leandro Costa
    November 9, 2012

    There’s a problem with jqm-config.js + with many options.
    JQM renders in different ways depending on their sizes [see http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.js line 8553] . If the widget is too big JQM creates a new page and changes to it. When the execution gets line 3928 JQM tries to call active.url.indexOf( dialogHashKey ) , but active.url is undefined. Does anybody know how to solve this problem?

    Reply
  7. Leandro Costa
    November 9, 2012

    In the last post I was talking about the SELECT widget.

    Reply
  8. dan
    November 13, 2012

    I have followed this really good tutorial which is a starter point for jQuery Mobile and Backbone.

    I understand due to backbone and jQueryMobile both having built in routing services that jQueryMobile has theirs switched off

    one of the main reasons why I wanted to use such a package (as well as the UI styling) was for the page transitions (ie pop or flip) but when I can to add these in to the index page of the attached example they did nothing (i am guessing due to the fact that its something is disabled)

    does anyone know a way round this and is my diagnostic correct?

    thanks

    Reply
  9. dan
    November 14, 2012

    favorite

    I have put this demo together

    http://demo.stg.brightonconsulting.net.au/templates/tests/backboneJQMProducts/

    at the moment only the top link ‘products json pull’ works.

    the routing to the first page works ok but when you refresh the page it doesnt show that page it just shows a blank page? (it is refreshes ok – do a cntr+f5 and you will see)

    has anyone had this before/does anyone know what the fix is?

    thanks

    dan

    Reply
  10. Daniel
    April 3, 2013

    Thank you so much! This tutorial is like an aspirin to me. I was struggling for many days trying to bring backbone and jqm to work. Somewhere I found a very similar approach to this, but as usual not complete. Your’s is working.

    I had to change one small thing in bookModel.js :

    define(['backbone'], function(Backbone){

    var Book=Backbone.Model.extend({
    //default attributes
    defaults:{
    id:””,
    name:’’,
    category:’’
    }
    });

    return Book;
    });

    Reply

Leave a Reply