Roll Your Own Framework With Backbone.js

Tweet

Let’s make a collective decision here. We’ll stop treating A-grade mobile phones as aliens, and see them for what they are: powerful, capable devices, running powerful rendering engines.

Throwing a framework at your app, such as jQuery Mobile, Sencha, or anything that gives you the popular iOS-like native look and feel, instantly boxes you into a set of choices, design and functionality-wise. Your app becomes a McDonalds burger: same as a bunch of other B-side apps that look and feel slightly worse than a native application. You can do way better than that, using what the browser in each of these devices provides.

We’ll look at how to do this, taking a minimalist approach using HTML5, CSS3 and Backbone.js. Minimalism is now trendy (your mileage may vary) for desktop web apps, but on mobile, the benefits are huge, and more importantly, noticeable.

Point your iPhone/Android to http://bm-example.heroku.com and have a play around. This example is taken from an app I’ve been working on, very slowly, for a while now. The implementation is, in turn, based on an example that comes with Zepto.js.

First Things First

Let’s understand the markup first. Each “page” in our app is defined by a <section> tag. Navigation will consist of us jumping from one section to another, so at any one time there’s only one section visible, which is how nearly every mobile web framework works in reality, though the way we’re doing it is quite minimal and semantic.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <section id="home"></section>
    <section id="settings"></section>
    <section id="shop-1"></section>
  </body>
</html>

Here’s the part of the CSS that handles the navigation. I’ve omitted the aesthetic parts to keep the discussion focussed, but you can see the code whole, not just the CSS, on the BuildMobile GitHub Backbone.js repo.

* { margin: 0; padding: 0; outline: 0; -webkit-box-sizing: border-box }

body {
  position: absolute;
  height: 100%;
  width: 100%;
  overflow-x: hidden;
  -webkit-perspective: 800;
  -webkit-text-size-adjust: none;
  -webkit-transform-style: preserve-3d;
  -webkit-user-select: none;
}

body > section {
  position: absolute;
  left: 0;
  width: 100%;
  opacity: 0;
  -webkit-transition: all 0.25s ease-in-out;
  -webkit-transform-style: preserve-3d;
  -webkit-transform: translate3d(100%,0%,0%);
}

body > section.current { -webkit-transform: translate3d(0%,0%, 0%); opacity: 1 }

The above says, roughly:

  • On every element, reset the margin, padding, and outline to zero. Also, let the element’s width be defined by where the border is, instead of it’s contents. This last setting is particularly handy in the context of mobile because there’ll be a lot of elements allowed to freely flow to the maximum width of the screen. If we added padding to any of those and left this out, overflow would occur.
  • Stretch the body to the maximum width, and every immediate child section along with it.
  • By default, every section is hidden. Only a section containing the class current will be visible.
  • We want to allow vertical scrolling while keeping horizontal scrolling out.
  • A transition should occur on every transitionable attribute. That’s gonna help us create a cool transition effect between pages.

Sounds kind of crazy, but that’s really all there is to our application as far as structuring it’s sections goes. But until we can press buttons and go from one place to another, we don’t have much. So let’s get that going with a Backbone Router and some pushState action.

var Router = Backbone.Router.extend({
  routes : {
    ''            : 'home',
    '/'           : 'home',
    '/shops/:id'  : 'shop',
    '/:id'        : 'page'
  },

  hideAll : function() {
    $('body > section').removeClass('current');
  },

  home : function() {
    this.hideAll();
    $('#home').addClass('current');
  },

  page : function(id) {
    this.hideAll();
    $('#' + id).addClass('current');
  }
});
App.placeAnchorHooks();
Backbone.history.start({ pushState: true });    

I said earlier that only one section would be visible at a time. That’s achieved by having the CSS class current assigned to the visible section. Since by default every section is hidden, as per the CSS we wrote earlier, all other sections remain hidden. The hideAll method removes the current class from whichever section has it, so we call that first before any other section has it assigned.

We defined three routes. A route for the home page, which is the first thing you’ll see when you get to the app, a route for matching the shops we’ll browse, and a catch-all route which will kick in when we specify anything else.

Note that should this be a brochure kind of app (just separate sections and navigation), we wouldn’t need anything else from Backbone other than a router. That’s one big up for the spirit of minimalism behind this framework. You can use the parts that you want, while leaving the others out.

Linking

Since we’re using pushState, we’re not talking about links in the technical sense. They’re not anchors, which use the href attribute to get the browser to make a new request, pushState is something else: you’re relying on JavaScript to manipulate the browser history by pushing URLs into it. Backbone, in turn, detects these changes and sends you to a “route” that matches the URL being pushed.

To make it clear: forget real anchors. Unless you’re concerned with making the app degrade to browsers that don’t support pushState, or JavaScript even, depending on the semantics of your markup, you may as well do away with them.

As of the time of writing, there are issues with how iOS handles real anchors, events being interrupted, and pushState. So for the sake of making things simple, I’ve devised a workaround: I’ll rely on an HTML5 attribute named data-href, then run a jQuery live call on every anchor or button that has it set, and hook a call to the router’s navigate on touchstart. It’s an earful, but the implementation is rather simple:

placeAnchorHooks : function() {
  $('[data-href]').live('touchstart', function() {
    App.Router.navigate( $(this).attr('data-href'), true );        
  });
}

Using touchstart instead of click makes a huge difference to the responsiveness of the app. As an exercise, replace it with click (you’ll have to clone the repository first) to see what happens. You’ll notice that for every tap, there’s a perceptible delay before the screen reacts to the touch. By using touchstart instead of click, we’re working around that.

In case you’re wondering, yes, we could do without pushState and rely instead on “hashbangs” to get things going. I’m doing this to illustrate how easy it is to have transparent pushState-enabled links in your app. This can be reused with non-mobile web apps too.

Settings

We’re allowing only one setting in this example. The user may specify keywords that the app will use to narrow down which stores it should show. This, however, can easily be extended to a number of attributes. Of particular note here, is how we’re keeping these preferences. I’ve overridden the sync method for the Settings Backbone model, so that it saves the attributes using localStorage.

This makes perfect sense when you realise we’re keeping preferences on a per-device basis. And until it becomes the norm for people to carry more than one mobile phone, that will do. The override consists of the following:

var Settings = Backbone.Model.extend({
  sync: function(method, model, options) {
    switch(method) {
      case 'create':
      case 'update':
        localStorage.setItem('Settings', JSON.stringify(model));
        break;
      case 'delete':
        localStorage.removeItem('Settings');
        break;
      case 'read':
        var settings = localStorage.getItem('Settings');
        model.attributes = (settings && JSON.parse(settings) || {});
        return model;
    }   
    return this;
  }

Views are Sub Applications

This example uses two Backbone Views: one for the home page, and one for the settings page. I won’t go into detail as to what Backbone Views are, but think of them as self-contained sub-applications inside your big application. The HomeView class has a base element, and all the visual stuff that’s related to it happens in this element. It also keeps an eye out for changes in the Settings model with this line:

App.Settings.bind('change', this.hideMenu, this);

That’s so we stop pestering the user with that bouncing exclamation mark once they’ve set some keywords.

The SettingsView class in turn has two jobs. First, it takes what is already in the Settings model (say, you’ve set your keywords before) and populates the text field with it, so the user can see what’s in there. Second, it grabs what is in the text field and puts that in the Settings model when we change the value by submitting the form.

Custom Pages

It’d be interesting to allow shop owners to customise the look of their mobile store. But only to an extent, because we don’t want to inflict another MySpace on the world. So we’ll allow them to pick themes, which consist of a few overrides that kick in through a CSS class. This is really easy to do, fortunately.

body > section.coffee {
  background: -webkit-gradient(radial, 50% center, 200, 50% center, 40, from(#a54e03), to(#bc7940)), #a54e03;
}
body > section.coffee h1,
body > section.coffee p   { text-shadow: 1px 1px 0 #000 }

The “coffee” theme is merely a variant of the original red theme, with a coffee-like colour. As a bonus I’ve added drop shadows to the text to improve the readability, since the light brown colour offers a lot less contrast than the original dark red, when considering the white text. The remaining example themes are all variants of this one.

So let’s say the store owner, when setting up her store, picked the “coffee" theme. In the example, we have a few hardcoded links in the HTML, a couple of working stores and only one with a little content in it. If we were loading these from a server via Ajax, we’d load the store’s preferences, detect the theme choice, and apply the corresponding CSS class to the <section> element.

In a nutshell, this is a recipe for customising pages in our little framework of sorts.

Rationale

You may be wondering why you’d go through all this when throwing a framework at it sounds so much simpler and quicker. The idea here is twofold: to have you understand that building this from scratch is very straightforward, and to give you a base boilerplate that you can keep around, so you can always start a new app from it.

You could either macro the contents of these files into your text editor, or write a script and create a mini-toolchain, or even manually copy this boilerplate to a new folder. Once you get that going, you’ll boot a new application as fast as you’d do with any other framework.

Performance

Some of you will notice that the JavaScripts are uncompressed, each separated into it’s own file. Keeping that in production is a terrible idea, especially if we’re talking about mobile applications where latency is so much more expensive. Should you deploy this as an application, make sure you minify and bundle your scripts, as this will make things significantly faster.

Another important consideration, along the same lines of minimising the number of HTTP requests a mobile browser needs to make to load your app, is making sure that any images forming part of the application, for example arrows for buttons, the application logo, and so on, are either:

  • Embedded in the CSS using Data URI. This will increase the size of the image a little, but you still stand to gain from it. The less HTTP requests your app needs to make, the faster it’ll be.
  • Converted into SVG and embedded in the HTML. That’s a personal favourite of mine. iOS 4 and 5, and Android Honeycomb all support SVG, so to stay on the safe side, if you expect you’d have to support browsers that don’t support SVG, go with the previous option.

Parting Words

At the risk of sparking rants, let me clarify one thing: there’s nothing essentially wrong with mobile frameworks. Well, some of them are very bad, but in general most of them offer support for old, featureless mobile browsers. Should you need to support those browsers, then by all means, give a framework a shot, as it certainly will save you time.

If, however, you are looking at having your application running on either Android or iOS, embrace the constraints. There are few constraints, and they are mostly design-centric, so roll your own framework using a minimalist targeted approach, with Backbone.js.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Damien

    Nice piece of code. Thanks!!

  • http://orbital.co.nz johans

    Concise and informative.

    Agree understanding more about the framework and unique result are huge benefits of rolling your own.

    Thanks.

  • Derrick

    Thanks. Really enjoy the terse, minimalist approach. Have you dealt with list data where scrolling must be addressed? I noticed in your demo, you aren’t doing any touch event handling, just treating everything as a link. Any thoughts on how your approach would deal with management of touch events or if anyone has built backbone.js modules for touch?

  • Randy

    In the example app, the “nav buttons” in the UL are too wide to easily allow a user to scroll vertically when that functionality is needed. Setting the body section margin to something wider than 10 px would allow for fat-fingered dudes like me to scroll without accidentally activating any of the list items.

  • http://www.giftsspace.com/blog George

    Heard about backbone provides MVC. Now this tutorial makes it more practical. Thanks for sharing.

  • http://joezimjs.com JoeZIm

    Another demonstration of the power of Backbone.js. After I’ve finished with my Design Patterns series on my JavaScript blog (http://joezimjs.com) I’ll make sure to throw some Backbone.js demonstrations and tutorials up.

  • http://gregpettit.ca Greg

    In no way do I mean this as nitpicky; just trying to be helpful for this great tutorial. But .live() is not only deprecated from jQuery 1.7+, but was already considered a poor performer to begin with. I would recommend using .on() as a delegator, or the older (not officially deprecated, but no longer recommended) .delegate().

    $(‘body’).on(‘touchstart’, ‘[data-href]‘, function() { … });

    In terms of performance… honestly, not much to be gained here. body as a listener is about as broad as document as listener (per live) and you might want to just use $(document) after all. But it’s not deprecated.

  • http://popdigital.ca AJ

    Just a heads up that I don’t believe you can use inline SVG images in iOS 4… See http://caniuse.com/svg-html5. SVGs as background images in your CSS should work on both iOS 4 and 5 though.

  • http://www.karmacrm.com John Paul Narowski

    Hi,

    Nice job on the post! It really resonated with me as I have done the Sencha Touch, JQuery mobile hustle myself and still don’t feel like I have found a home. I really want to unify my codebases so that I can have the best foot forward in both Mobile and Web without having completely separate codebases. I think the companies that do this right will have a huge leg up on those that don’t and glad to hear there is someone else out there putting themselves under the bus to make it happen.

    I’d love to chat with you more about this post and possibly see if you had any freelance time. I could really use a mind like yours to help me get my mobile and web situation sorted out and more robust. Either way I appreciate the time you put into this and wish you luck in the framework hustle.

  • http://www.wordpressguru.com.au Wordpress Developer

    Thanks Julio … A good intro to Backbone.js that was I was looking at … I am loving what I am reading at your site … Bookmarked …

  • http://jessebreuer.tumblr.com/ Jesse

    When I try to run the downloaded application I get a blank screen, until I comment out “opacity: 0″ from the body > section selector in the css. Is there supposed to be a section that loads by default? Also I needed to remove the opening slashes from the links to the javascript and css files, for example changing src=”/js/jquery-1.6.4.min.js” to src=”js/jquery-1.6.4.min.js”