GOOD NEWS

GOOD NEWS
Ask For The Truth...

Sunday, September 2, 2012

Sample Application with Angular.js

Sample Application with Angular.js:
After I blogged a three-part Backbone.js tutorial (part 1, part 2, part 3), a number of people asked me to try Angular.js. So I decided to take it for a test drive. I thought it would be interesting to rebuild with Angular.js the Wine Cellar application I had built with Backbone.



If you are new to my Wine Cellar application, it is a simple CRUD app that allows you to manage (create, update, delete) wines in a Wine Cellar. The data is stored in a MySQL database that the client application accesses through a simple RESTful API. This seemed to be a good fit since CRUD applications are often positioned as the sweet spot for Angular.js.
You can run the application here. The UI is intentionally plain to keep the focus on the key learning points. For obvious reasons, this online version is “read-only”. You can download the fully enabled version here.

Application Walkthrough

The best way to get started with Angular.js is to go through the excellent tutorial that is part of the documentation, so I’ll only provide a high level overview of my code here.
Like the Backbone.js implementation, the Angular.js version is a deep-linkable single page application. index.html is defined as follows:
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org">
<head>
<title>Angular Cellar</title>
<link rel="stylesheet" href="css/styles.css" />
</head>

<body ng:controller="RouteCtrl">

<div id="header">
    Angular Cellar
    <button ng:click="addWine()">New Wine</button>
</div>

<div id="sidebar">
    <ng:include src="'tpl/wine-list.html'"></ng:include>
</div>

<div id="content">
    <ng:view></ng:view>
</div>

<script src="lib/angular-0.9.19.min.js" ng:autobind></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>

</body>

</html>
The application is driven by a set of Controllers that I defined in controllers.js as follows:
function RouteCtrl($route) {

    var self = this;

    $route.when('/wines', {template:'tpl/welcome.html'});

    $route.when('/wines/:wineId', {template:'tpl/wine-details.html', controller:WineDetailCtrl});

    $route.otherwise({redirectTo:'/wines'});

    $route.onChange(function () {
        self.params = $route.current.params;
    });

    $route.parent(this);

    this.addWine = function () {
        window.location = "#/wines/add";
    };

}

function WineListCtrl(Wine) {

    this.wines = Wine.query();

}

function WineDetailCtrl(Wine) {

    this.wine = Wine.get({wineId:this.params.wineId});

    this.saveWine = function () {
        if (this.wine.id > 0)
            this.wine.$update({wineId:this.wine.id});
        else
            this.wine.$save();
        window.location = "#/wines";
    }

    this.deleteWine = function () {
        this.wine.$delete({wineId:this.wine.id}, function() {
            alert('Wine ' + wine.name + ' deleted')
            window.location = "#/wines";
        });
    }

}
RouteCtrl defines the routes of the application. Each route is defined by a template that is rendered in <ng:view> inside index.html. There can only be one <ng:view> in a document (more on that later). For example, here is the wine-list.html template:
<ul ng:controller="WineListCtrl">
    <li ng:repeat="wine in wines">
        <a href='#/wines/{{ wine.id }}'>{{ wine.name }}</a>
    </li>
</ul>
The WineListCtrl and WineDetailCtrl controllers provide access to the data using a service defined in services.js as follows:
angular.service('Wine', function ($resource) {
    return $resource('api/wines/:wineId', {}, {
        update: {method:'PUT'}
    });
});
Services provide a great way to abstract your data access logic, and to easily change it without impacting the rest of the application. For example, you could easily change the Wine service to use a Mock service instead of a RESTful service.

Impressions

I was able to build this application in a very limited amount of time with no prior knowledge of the framework. The data-binding implementation is nice, and, in general, the amount of boilerplate code you have to write is very limited. Frameworks are often a matter of style and personal preferences. Angular.js takes a more declarative approach than other frameworks. Based on this initial experience, I would also describe it as more prescriptive: I didn’t have to spend a lot of time wondering what was the best way to do things as Angular.js tends to have clear utilization patterns. I haven’t spend enough time with the framework to determine if that comes at the cost of less control, and I’d love to hear what other people are thinking.
The only problem I ran into while building the application is the “one route / one view” coupling I alluded to above. As suggested in this thread, you can use <ng:include> to bind partials in the page. The same thread also indicates that multiple <ng:views> per route definition will be supported in the future. I was able to handle the simple UI requirements of this app using one <ng:view> and one <ng:include>. For more complex applications, I’d love the routing infrastructure to allow me to easily and arbitrarily add, remove, change, or animate different elements of the DOM when the route changes in order to support deeper UI transformations. I’m sure there are ways to do this. If Angular.js folks are reading this post, I’d love to hear what they think and what’s coming.

Download

The application is available on GitHub here.
You will need the RESTful services to run this application. A PHP version (using the Slim framework) is available as part of the download. If you want to test the application with a Java back-end, you can download the Backbone version here, and reuse its JAX-RS back-end.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Sample Mobile App with Backbone.js, PhoneGap, and a Local Database

Sample Mobile App with Backbone.js, PhoneGap, and a Local Database:
In my previous post, I shared a simple Wine Cellar application built with Backbone.js and packaged as a mobile app with PhoneGap. That version of the application gets its data from a set of RESTful services, which means that you can only use it while online.
In this post, we explore an offline version of the same application: this new version gets its data from your device’s local database using the PhoneGap SQL database API.
By default, Backbone.js Models access their data using RESTful services defined by the Models “url” and “urlRoot” attributes. But Backbone.js also makes it easy and elegant to change your Models data access mechanism: All you have to do is override the Backbone.sync method. From the Backbone.js documentation:
Backbone.sync is the function that Backbone calls every time it attempts to read or save a model to the server. By default, it uses (jQuery/Zepto).ajax to make a RESTful JSON request. You can override it in order to use a different persistence strategy, such as WebSockets, XML transport, or Local Storage.
The documentation includes a useful example that shows how to replace the default Backbone.sync implementation with localStorage-based persistence where models are saved into JSON objects.
In our application, we will replace the default Backbone.sync implementation with SQL-based persistence using the device’s local database.



First, we create a data access object that encapsulates the logic to read (find), create, update, and delete wines. This simple application is read-only and only needs to retrieve collections. So, I only implemented the findAll() method and stubbed the other methods (find, create, update, destroy). WineDAO also has a “populate” function to populate the wine table with sample data.
window.WineDAO = function (db) {
    this.db = db;
};

_.extend(window.WineDAO.prototype, {

    findAll:function (callback) {
        this.db.transaction(
            function (tx) {
                var sql = "SELECT * FROM wine ORDER BY name";
                tx.executeSql(sql, [], function (tx, results) {
                    var len = results.rows.length;
                    var wines = [];
                    for (var i = 0; i < len; i++) {
                        wines[i] = results.rows.item(i);
                    }
                    callback(wines);
                });
            },
            function (tx, error) {
                alert("Transaction Error: " + error);
            }
        );
    },

    create:function (model, callback) {
//        TODO: Implement
    },

    update:function (model, callback) {
//        TODO: Implement
    },

    destroy:function (model, callback) {
//        TODO: Implement
    },

    find:function (model, callback) {
//        TODO: Implement
    },

//  Populate Wine table with sample data
    populate:function (callback) {
        this.db.transaction(
            function (tx) {
                console.log('Dropping WINE table');
                tx.executeSql('DROP TABLE IF EXISTS wine');
                var sql =
                    "CREATE TABLE IF NOT EXISTS wine ( " +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                        "name VARCHAR(50), " +
                        "year VARCHAR(50), " +
                        "grapes VARCHAR(50), " +
                        "country VARCHAR(50), " +
                        "region VARCHAR(50), " +
                        "description TEXT, " +
                        "picture VARCHAR(200))";
                console.log('Creating WINE table');
                tx.executeSql(sql);
                console.log('Inserting wines');
                tx.executeSql("INSERT INTO wine VALUES (1,'CHATEAU DE SAINT COSME','2009','Grenache / Syrah','France','Southern Rhone / Gigondas','The aromas of fruit and spice give one a hint of the light drinkability of this lovely wine, which makes an excellent complement to fish dishes.','saint_cosme.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (2,'LAN RIOJA CRIANZA','2006','Tempranillo','Spain','Rioja','A resurgence of interest in boutique vineyards has opened the door for this excellent foray into the dessert wine market. Light and bouncy, with a hint of black truffle, this wine will not fail to tickle the taste buds.','lan_rioja.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (3,'MARGERUM SYBARITE','2010','Sauvignon Blanc','USA','California Central Cosat','The cache of a fine Cabernet in ones wine cellar can now be replaced with a childishly playful wine bubbling over with tempting tastes of black cherry and licorice. This is a taste sure to transport you back in time.','margerum.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (4,'OWEN ROE \"EX UMBRIS\"','2009','Syrah','USA','Washington','A one-two punch of black pepper and jalapeno will send your senses reeling, as the orange essence snaps you back to reality. Do not miss this award-winning taste sensation.','ex_umbris.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (5,'REX HILL','2009','Pinot Noir','USA','Oregon','One cannot doubt that this will be the wine served at the Hollywood award shows, because it has undeniable star power. Be the first to catch the debut that everyone will be talking about tomorrow.','rex_hill.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (6,'VITICCIO CLASSICO RISERVA','2007','Sangiovese Merlot','Italy','Tuscany','Though soft and rounded in texture, the body of this wine is full and rich and oh-so-appealing. This delivery is even more impressive when one takes note of the tender tannins that leave the taste buds wholly satisfied.','viticcio.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (7,'CHATEAU LE DOYENNE','2005','Merlot','France','Bordeaux','Though dense and chewy, this wine does not overpower with its finely balanced depth and structure. It is a truly luxurious experience for the senses.','le_doyenne.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (8,'DOMAINE DU BOUSCAT','2009','Merlot','France','Bordeaux','The light golden color of this wine belies the bright flavor it holds. A true summer wine, it begs for a picnic lunch in a sun-soaked vineyard.','bouscat.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (9,'BLOCK NINE','2009','Pinot Noir','USA','California','With hints of ginger and spice, this wine makes an excellent complement to light appetizer and dessert fare for a holiday gathering.','block_nine.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (10,'DOMAINE SERENE','2007','Pinot Noir','USA','Oregon','Though subtle in its complexities, this wine is sure to please a wide range of enthusiasts. Notes of pomegranate will delight as the nutty finish completes the picture of a fine sipping experience.','domaine_serene.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (11,'BODEGA LURTON','2011','Pinot Gris','Argentina','Mendoza','Solid notes of black currant blended with a light citrus make this wine an easy pour for varied palates.','bodega_lurton.jpg')");
                tx.executeSql("INSERT INTO wine VALUES (12,'LES MORIZOTTES','2009','Chardonnay','France','Burgundy','Breaking the mold of the classics, this offering will surprise and undoubtedly get tongues wagging with the hints of coffee and tobacco in perfect alignment with more traditional notes. Breaking the mold of the classics, this offering will surprise and undoubtedly get tongues wagging with the hints of coffee and tobacco in perfect alignment with more traditional notes. Sure to please the late-night crowd with the slight jolt of adrenaline it brings.','morizottes.jpg')");
            },
            function (tx, error) {
                alert('Transaction error ' + error);
            },
            function (tx) {
                callback();
            }
        );
    }
});
I then added a “dao” attribute to the Models to specify which data object should be used to access their underlying data.
window.Wine = Backbone.Model.extend({
 urlRoot: "http://coenraets.org/backbone-cellar/part1/api/wines",
    dao: WineDAO
});

window.WineCollection = Backbone.Collection.extend({
 model: Wine,
 url: "http://coenraets.org/backbone-cellar/part1/api/wines",
    dao: WineDAO
});
NOTE: The application doesn’t need the url and urlRoot attributes anymore, but you can imagine a more sophisticated version of the app that would work with the RESTful services while online and fall back to the local database while offline.
With that infrastructure in place, we can now override Backbone.sync as follows:
Backbone.sync = function (method, model, options) {

    var dao = new model.dao(window.db);

    switch (method) {
        case "read":
            if (model.id)
                dao.find(model, function (data) {
                    options.success(data);
                });
            else
                dao.findAll(function (data) {
                    options.success(data);
                });
            break;
        case "create":
            dao.create(model, function (data) {
                options.success(data);
            });
            break;
        case "update":
            dao.update(model, function (data) {
                options.success(data);
            });
            break;
        case "delete":
            dao.destroy(model, function (data) {
                options.success(data);
            });
            break;
    }

};
And finally, we need to open the database (and populate it with sample data) at startup:
window.startApp = function () {
    var self = this;
    window.db = window.openDatabase("WineCellar", "1.0", "WineCellar Demo DB", 200000);
    var wineDAO = new WineDAO(self.db);
    wineDAO.populate(function () {
        this.templateLoader.load(['wine-list', 'wine-details', 'wine-list-item'], function () {
            self.app = new AppRouter();
            Backbone.history.start();
        });
    })
}

Download

I added this version of the application to the backbone-cellar GitHub repository here.
NOTE: You will have to create a PhoneGap project (see documentation here) or use the hosted build service to package the code as a native app for different mobile platforms.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Using Backbone.js with jQuery Mobile

Using Backbone.js with jQuery Mobile:
Backbone.js is an architectural framework that helps you write well-structured Web applications. It is not, however, a user interface framework and it therefore doesn’t help you with the way your application looks.
Backbone’s confined scope is a good thing: it’s lightweight, non-intrusive, not coupled to things you don’t need, and it lets you use the UI toolkit of your choice or simply roll your own styles and widgets. In my previous post, I demonstrated how to use Twitter Bootstrap on top of Backbone.
Quest for a Mobile UI Toolkit
After that post, I wanted to create a mobile version of the same application; a version that I could package with PhoneGap and that would look and behave like a native app. Twitter Bootstrap can probably be tweaked for that purpose as well, but I was looking for a UI toolkit dedicated to providing native looking controls and behaviors on mobile devices.


Another Way to Use jQuery Mobile
jQuery Mobile (jQM) is one option that I’ve explored before (here and here), but it fits more in the category of full-stack frameworks that tie together architectural structure and UI controls and behaviors. John Bender, my colleague at Adobe and member of the jQuery Mobile team, recently pointed out to me that you can disable the routing and navigation capabilities of jQM, and essentially use it as a pure UI framework on top of other architectural frameworks like Backbone.js.
Sample Application
I ended up spending a decent amount of time trying different things to get the two frameworks to play well together without stepping on each other. To save you some headaches if you are trying to do the same, I put together a simple application with the basic setup to combine Backbone (for the application structure and “routing”) and jQuery Mobile (for its styles and widgets).
NOTE: Another approach would be to use jQM’s “routing” instead of Backbone’s. Ben Nolan has an example of this approach here. I prefer to use Backbone’s routing because I find it more flexible and less “page-centric”.
Here is the app:
Click here to run the application in a separate window. The source code is available in this GitHub repository.
How it works
The key to this approach is to disable jQuery Mobile’s “routing”: In other words, you need to tell jQuery Mobile not to handle links, hash tag changes, and so on. I isolated that code in jqm-config.js:
$(document).bind("mobileinit", function () {
    $.mobile.ajaxEnabled = false;
    $.mobile.linkBindingEnabled = false;
    $.mobile.hashListeningEnabled = false;
    $.mobile.pushStateEnabled = false;
});
If jQuery Mobile is not in charge of page navigation, you also have to manually remove the pages from the DOM when they are not used anymore. Here is one way to do it:
$('div[data-role="page"]').live('pagehide', function (event, ui) {
    $(event.currentTarget).remove();
});
With this configuration in place, you use Backbone’s routing as usual:
var AppRouter = Backbone.Router.extend({

    routes:{
        "":"home",
        "page1":"page1",
        "page2":"page2"
    },

    home:function () {
        this.changePage(new HomeView());
    },

    page1:function () {
        this.changePage(new Page1View());
    },

    page2:function () {
        this.changePage(new Page2View());
    },

    changePage:function (page) {
        $(page.el).attr('data-role', 'page');
        page.render();
        $('body').append($(page.el));
        $.mobile.changePage($(page.el), {changeHash:false});
    }

});
Is this the right stack?
I like the idea of a lightweight architectural framework combined with a UI toolkit. Backbone + Twitter Bootstrap felt right because the two frameworks have different areas of concern and complement each other very well. I was happy to see you could decouple jQM from its navigation infrastructure. However, that’s probably not the main “design center” at this point. I think it would be interesting for jQM to focus on that utilization scenario as well. At the end of the day, frameworks are often a matter of personal preferences, and not all applications are equal. So try it, see if it works for you, and share your experience. What UI toolkit are you using?
Source Code
The source code is available in this repository on GitHub.
A More Real-Life Application
In my next post, I’ll share a Backbone.js + jQuery Mobile version of the Employee Directory application first explored with Backbone.js + Twitter Bootstrap.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Sample App with Backbone.js and Twitter Bootstrap

Sample App with Backbone.js and Twitter Bootstrap:
Backbone.js is a lightweight JavaScript framework that provides the basic infrastructure (Model, Collection, View, and Router classes) to bring structure to your Web applications.
Twitter Bootstrap is a UI toolkit that provides simple and flexible HTML, CSS, and Javascript to implement popular user interface components and interactions.
In other words, Backbone.js and Twitter Bootstrap focus on different areas of your application: core architecture and user interface respectively. Because of their well-defined and non-overlapping scope, Backbone.js and Twitter Bootstrap work well together. In general, I find a lightweight architectural framework and a UI toolkit to be a powerful combination, and an interesting alternative to full-stack frameworks: it gives you the flexibility to choose the library you like (if any) in the respective areas of your application.

The Sample Application

To give this combination a try, I put together a new sample application that uses Backbone.js to organize the code, and Twitter Bootstrap to organize the UI. The application is an Employee Directory that allows you to look for employees by name, view the details of an employee, and navigate up and down the Org Chart by clicking the employee’s manager or any of his/her direct reports.
You can run the application here.





Backbone Directory is a single page application: index.html is essentially empty. Views are injected into and removed from the DOM as needed. Even though it is a single page application, the Backbone.js Router makes it easy to keep the different states of the app “bookmarkable” and “deep-linkable”.

Twitter Bootstrap highlights

“Backbone Directory” uses a number of the Twitter Bootstrap styles, components, and interactions: the 12-column grid with nested columns, a “Navbar”, a “Search Form” with dropdown, the dropdown plugin, the Glyphicons icons, Info and Warning alerts, a “Well”, etc.

Backbone.js highlights

If you are new to Backbone.js, you may want to start with the tutorial (part 1, part 2, part 3, and postface) I blogged recently. “Backbone Directory” includes some interesting elements not covered in the tutorial:
  • One-to-Many association. A one-to-many (Manager-to-Employees) association is defined in the Employee model (model/employeemodel.js) as a collection of employees (the direct reports). That collection is lazily fetched in the render() function of EmployeeFullView (view/employeedetails.js).
  • Composite View. EmployeeFullView (views/employeedetails.js) is an example of a composite view. Its render() function instantiates two subviews: EmployeeView and EmployeeListView (to display the employee’s direct reports).

Source Code

The source code is available in this repository on GitHub.
Your feedback and comments are appreciated.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Using Backbone.js with jQuery Mobile

Using Backbone.js with jQuery Mobile:
Backbone.js is an architectural framework that helps you write well-structured Web applications. It is not, however, a user interface framework and it therefore doesn’t help you with the way your application looks.
Backbone’s confined scope is a good thing: it’s lightweight, non-intrusive, not coupled to things you don’t need, and it lets you use the UI toolkit of your choice or simply roll your own styles and widgets. In my previous post, I demonstrated how to use Twitter Bootstrap on top of Backbone.
Quest for a Mobile UI Toolkit
After that post, I wanted to create a mobile version of the same application; a version that I could package with PhoneGap and that would look and behave like a native app. Twitter Bootstrap can probably be tweaked for that purpose as well, but I was looking for a UI toolkit dedicated to providing native looking controls and behaviors on mobile devices.


Another Way to Use jQuery Mobile
jQuery Mobile (jQM) is one option that I’ve explored before (here and here), but it fits more in the category of full-stack frameworks that tie together architectural structure and UI controls and behaviors. John Bender, my colleague at Adobe and member of the jQuery Mobile team, recently pointed out to me that you can disable the routing and navigation capabilities of jQM, and essentially use it as a pure UI framework on top of other architectural frameworks like Backbone.js.
Sample Application
I ended up spending a decent amount of time trying different things to get the two frameworks to play well together without stepping on each other. To save you some headaches if you are trying to do the same, I put together a simple application with the basic setup to combine Backbone (for the application structure and “routing”) and jQuery Mobile (for its styles and widgets).
NOTE: Another approach would be to use jQM’s “routing” instead of Backbone’s. Ben Nolan has an example of this approach here. I prefer to use Backbone’s routing because I find it more flexible and less “page-centric”.
Here is the app:
Click here to run the application in a separate window. The source code is available in this GitHub repository.
How it works
The key to this approach is to disable jQuery Mobile’s “routing”: In other words, you need to tell jQuery Mobile not to handle links, hash tag changes, and so on. I isolated that code in jqm-config.js:
$(document).bind("mobileinit", function () {
    $.mobile.ajaxEnabled = false;
    $.mobile.linkBindingEnabled = false;
    $.mobile.hashListeningEnabled = false;
    $.mobile.pushStateEnabled = false;
});
If jQuery Mobile is not in charge of page navigation, you also have to manually remove the pages from the DOM when they are not used anymore. Here is one way to do it:
$('div[data-role="page"]').live('pagehide', function (event, ui) {
    $(event.currentTarget).remove();
});
With this configuration in place, you use Backbone’s routing as usual:
var AppRouter = Backbone.Router.extend({

    routes:{
        "":"home",
        "page1":"page1",
        "page2":"page2"
    },

    home:function () {
        this.changePage(new HomeView());
    },

    page1:function () {
        this.changePage(new Page1View());
    },

    page2:function () {
        this.changePage(new Page2View());
    },

    changePage:function (page) {
        $(page.el).attr('data-role', 'page');
        page.render();
        $('body').append($(page.el));
        $.mobile.changePage($(page.el), {changeHash:false});
    }

});
Is this the right stack?
I like the idea of a lightweight architectural framework combined with a UI toolkit. Backbone + Twitter Bootstrap felt right because the two frameworks have different areas of concern and complement each other very well. I was happy to see you could decouple jQM from its navigation infrastructure. However, that’s probably not the main “design center” at this point. I think it would be interesting for jQM to focus on that utilization scenario as well. At the end of the day, frameworks are often a matter of personal preferences, and not all applications are equal. So try it, see if it works for you, and share your experience. What UI toolkit are you using?
Source Code
The source code is available in this repository on GitHub.
A More Real-Life Application
In my next post, I’ll share a Backbone.js + jQuery Mobile version of the Employee Directory application first explored with Backbone.js + Twitter Bootstrap.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Employee Directory Sample App with Backbone.js and jQuery Mobile

Employee Directory Sample App with Backbone.js and jQuery Mobile:
Here is a mobile version of my Backbone.js Employee Directory application using jQuery Mobile as the UI toolkit.
As described in my previous post, jQuery Mobile was (at least initially) intended as a full-stack framework as opposed to a pure UI toolkit like Twitter Bootstrap. As such, it overlaps with the Backbone.js infrastructure in some areas. In particular, the URL routing feature provided by both frameworks may clash. The approach used here is to disable the routing and navigation capabilities of jQuery Mobile, and essentially use it as a pure UI framework on top of Backbone.js. This approach and its caveats are described in my previous post.







Click here to run the application.
A Backbone.js + Twitter Bootstrap version of this application is available here.
Source Code
I updated the backbone-directory GitHub repository to include this version: It is available in the jquerymobile directory while the Twitter Bootstrap version is available in the web directory.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

Building Mobile Apps with HTML and a Local Database

Building Mobile Apps with HTML and a Local Database:

After my recent post, Crafting Native Looking iOS Apps with HTML, a number of you asked for an offline version that would use a Local Database (instead of the simple in-memory store) and provide a mechanism to automatically keep the local database in sync with a server database.



I’ll save automatic data synchronization strategies for a future post, but here is the first step: an “offline” version of the application that uses the device’s or the browser’s local database as its data provider. This version still uses Backbone.js as its architectural framework. Backbone.js makes it easy to change its default data access mechanism (which assumes RESTful services). You just replace the default Backbone.sync implementation and provide your own data access logic: in this case, some local SQL logic.
Web SQL vs IndexedDB
As you probably know, there have been two competing database APIs for HTML. From the W3C web site:
  • The Web SQL specification defines an API for storing data in databases that can be queried using a variant of SQL. This specification is no longer in active maintenance and the Web Applications Working Group does not intend to maintain it further.
  • The Indexed Database specification defines APIs for a database of records holding simple values and hierarchical objects. It is a working draft, and “work in progress”.
Even though the W3C is no longer actively maintaining the spec, this application uses the Web SQL API because, as a mobile app, its two main target platforms are iOS and Android, which both currently support Web SQL but not IndexedDB. More detailed platform support information can be found on caniuse.com (Web SQL and IndexedDB).
Chrome, Safari, and Opera on the desktop also support Web SQL, which means that you can run the application in these browsers. Try it here. For example, using the Chrome Developer Tools you could debug the application and inspect the database as shown in this screenshot:

Firefox and IE don’t support Web SQL. You could easily create an alternative version of EmployeeDAO (described below) that uses IndexedDB instead. You could also create a version of the application that uses either Web SQL or IndexedDB depending on the platform it’s running on.
Code Highlights
The source code is available in the localdb folder of the backbone-directory repository on GitHub. Here is a quick walkthrough…
The data access logic is encapsulated in EmployeeDAO, which also has a “populate” function to populate the employee table with sample data.
directory.dao.EmployeeDAO = function(db) {
    this.db = db;
};

_.extend(directory.dao.EmployeeDAO.prototype, {

    findByName: function(key, callback) {
        this.db.transaction(
            function(tx) {

                var sql = "SELECT e.id, e.firstName, e.lastName, e.title, count(r.id) reportCount " +
                    "FROM employee e LEFT JOIN employee r ON r.managerId = e.id " +
                    "WHERE e.firstName || ' ' || e.lastName LIKE ? " +
                    "GROUP BY e.id ORDER BY e.lastName, e.firstName";

                tx.executeSql(sql, ['%' + key + '%'], function(tx, results) {
                    var len = results.rows.length,
                        employees = [],
                        i = 0;
                    for (; i < len; i = i + 1) {
                        employees[i] = results.rows.item(i);
                    }
                    callback(employees);
                });
            },
            function(tx, error) {
                alert("Transaction Error: " + error);
            }
        );
    },

    findById: function(id, callback) {
        // removed for brevity
    },

    findByManager: function(managerId, callback) {
        // removed for brevity
    },

    populate: function(callback) {
        // removed for brevity
    }
});
Models are annotated with a “dao” attribute to indicate which data object to use to access their underlying data.
directory.models.Employee = Backbone.Model.extend({

    dao: directory.dao.EmployeeDAO,

});

directory.models.EmployeeCollection = Backbone.Collection.extend({

    dao: directory.dao.EmployeeDAO,

    model: directory.models.Employee,

});
With that infrastructure in place, you can then override Backbone.sync to access data from the local database instead of RESTful services:
Backbone.sync = function(method, model, options) {

    var dao = new model.dao(directory.db);

    if (method === "read") {
        if (model.id) {
            dao.findById(model.id, function(data) {
                options.success(data);
            });
        } else if (model.managerId) {
            dao.findByManager(model.managerId, function(data) {
                options.success(data);
            });
        }
        // removed for brevity
    }

};
Source Code
The source code is available in the localdb folder of the backbone-directory repository on GitHub.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");