avatar

Derek Zeng

A programmer

Understanding backbone 1

by coderek

I started using backbone in my project to build a portal for admin users. It's my first attemp using backbone seriously, though I have played with it for quite a while.

In backbone, the entry point of the application is Backbone.history.start().

This function takes a hash as argument, which consist of a html5 feature called pushState. Setting this attribute to true can let backbone use normal url style in the form of "/page1/page2". Otherwise, the url of backbone pages have to be in the form of "#page1/page2". But this is only supported in the html5 capable browsers, specifically excluding IE8. So I just use it without pushstate by leaving arguments blank.

I have to define a router object before the page load. The router is like ruby on rails routes, but significantly simplified. This is my router:

Router = Backbone.Router.extend
  routes:
    "": "posts"
    "posts": "posts"
    "posts/:title": "posts"
    "feeds": "feeds"

  root : -> @getView("Home").render()
  posts: (title)-> @getView("Posts").render(title)
  feeds: -> @getView("Feeds").render()

  getView :(name)->
    if _.has Site, name
      return Site[name]
    else
      Site[name] = new Site.Views[name]

    return Site[name]


Site.route = new Router

Backbone.history.start()

The routes attribute of Router object defines the route and callbacks. The route will be the strings behind # sign. It can contain vairiables in the form of :vairable_name. Then the vairiable can be accessed from the callback arguments.

There are several ways to invoke the routes.

  1. type the url directly from browser's address bar in the form of http://xxxxx/#page1. This is the same as user click a link containing the url, be it relative or absolute.
  2. call Router object's navigate method. e.g. Site.router.navigate("help")

Once the route matched, the callbacks will be invoked. Since I use backbone to render my entire page, my entire page will be re-rendered. If there is no ajax going on within the page refresh, the re-rendering will be very fast. Otherwise, it will have to wait until the ajax call completes. Of course, I can optimize this part, but the basic idea is the same.

P.S. if one is creating a one page app, he may not need to use router.


Here is the basic structure of my backbone application. For development, all files are loaded separately. For production, they will be concatenated, minified and uglified.

    <!-- libraries -->
    <script src="js/lib/jquery.js"></script>
    <script src="js/lib/jquery.cookie.js"></script>
    <script src="js/lib/underscore.js"></script>
    <script src="js/lib/backbone.js"></script>
    <script src="js/lib/bootstrap.js"></script>

    <!-- end libraries  -->

    <!-- application config -->
    <script src="js/config.js"></script>
    <script src="js/helper.js"></script>

    <!-- models -->
    <script src="js/models/session.js"></script>
    <script src="js/models/user.js"></script>

    <!-- collections  -->
    <script src="js/collections/users.js"></script>

    <!-- views -->
    <script src="js/views/site.js"></script>
    <script src="js/views/users.js"></script>
    <script src="js/views/home.js"></script>

    <!-- router  -->
    <script src="js/routers/router.js"></script>

The top level object is defined in the application config.

var Site = {
  Models: {},
  Views: {},
  Collections: {},
}

Site is the only object I expose to the global scope. Subsequent creation of views, models and collections will be under this object. Thus, I have to load this config file before other backbone objects.

For view objects, I store both instance object and class object. The view instance is store directly under Site. I call it "#{viewname}View". e.g. for Site.Views.User, the instance will be Site.usersView. I follow this convention, so that I can write dynamic view dispatcher. This is the getView function in my router file.

I divide the view files into two types: view page and view element. View page is the view object that controls an entire page and it's the object called by router. View element is the view object that can be insert into part of a page multiple times.

For the view page, I use el element to point to the layout element where the page is attached to. And when the render function is called, I will dump all the page UI into this el element.

Site.Views.Feeds = Backbone.View.extend
  el: "#content"

  initialize: ->
    @feeds = new Site.Collections.Feeds

  events:
    "click .add_feed": "addFeed"

  addFeed:-> 
    ...

  template :
    """
    <fieldset>
    <legend>Feed:
    <input class=new_feed type=text /><button class="add_feed">submit</button>
      </legend>
    </fieldset>
    <div class=feeds>
    </div>
    """
  ...

  render:()->
    that = @
    @$el.html(@template)
    @feeds.fetch success:(e)->
      feeds = _.map e.models, (f)->
        that.$el.find(".feeds").append(_.template(that.feed, {f: f.toJSON()}))

      $(".feed").click ()->
        return if $(this).find(".feed_detail").length > 0 
        $(this).append("<div class='feed_detail'>Loading...</div>")
        that.getFeed($(this).attr("data-url"), $(this).find(".feed_detail"))

  ...

I embed HTML template inside my view script for easy reading. This is one of a few great advantages of using coffeescript writing javascript. To write template HTML using javascript is definitely horrible task.

The underscore library used here is also super useful. It extends the functionalities of javascript greatly by providing useful functions manipulating objects, array and string.

(End of article)