Understanding Backbone.js – Simple Example

I’m by no means an expert in Backbone.js.  I created this post because I had difficulty understanding backbone, and I hope that the information that I provide will help others grasp Backbone.js a little faster.  This post is directed to individuals who understand the concepts of Backbone.js, but are having a difficult time implementing a simple solution.

This post provides a simple example on Backbone.js Routers, Models and Collections and Views.  I’m going to try to make this example extremely simple. But also show key Backbone.js concepts

I’m probably not the best writer.  It would take me days, if I were to validate all the grammar, spelling and such.  So I’m just going to try to get it done.  Even though the grammar and such may not be perfect, the technical parts of the article should be correct.  I hope you enjoy.

A few months after writting this post, my priorites and technology direction changed. For probably 6 months I did little work with Backbone.js. When I came back to Backbone.js in November, I was a little lost. I had to become familar with the technology once again. I thought it would be a good opportunity to write about Backbone.js basics. Here are posts where I describe the basics of Backbone.js.

This post will be based on a contrived idea of displaying a list of movies at a theater.

The complete solution can be downloaded from here:
Source – Backbone Theater Zip

Here’s the structure of my website.

In the image above, there are 3 files (Backbone, jQuery, and Underscore) in the lib directory.  Backbone has a hard dependency on underscore and must be referenced, but a soft dependency on jQuery.

This example will not access a server, so to simulate the retrieval of data from the server I have a file name movies.json in the data directory.  Here’s an example of the file:

[
    {
        "Id": "BVwi1",
        "Name": "Bag It",
        "AverageRating": 4.6,
        "ReleaseYear": 2010,
        "Url": "http://www.netflix.com/Movie/Bag_It/70153545",
        "Rating": "NR"
    },
    {
        "Id": "BW1Ss",
        "Name": "Lost Boy: The Next Chapter",
        "AverageRating": 4.6,
        "ReleaseYear": 2009,
        "Url": "http://www.netflix.com/Movie/Lost_Boy_The_Next_Chapter/70171826",
        "Rating": "NR"
    },
….]

Theater.htm will be the file the user navigate to in the browser.  This file is fairly simple and we will look at it in more detail shortly.

Last but not least in the script directory is the main.js file.  This file is where all the magic happens.

To get started, we will look at the theater.htm file

<html>
<head>
	<title></title>
</head>
<body>
    <h1>My Theater</h1>
    
    <script src="scripts/libs/jquery-1.7.1.js" type="text/javascript"></script>
    <script src="scripts/libs/underscore.js" type="text/javascript"></script>
    <script src="scripts/libs/backbone.js" type="text/javascript"></script>
    <script src="scripts/main.js" type="text/javascript"></script>   
</body>
</html>

All theater.htm file does is reference scripts. We are currently referencing jQuery, Underscore, and Backbone.  Since there are dependencies for the scripts, it’s important that these files are referenced in this order.

The last script referenced is main.js; lets create it.

Create a new file call main.js in the scripts directory

It’s import to namespace our application so that there is a less possibility of name collision.  To start out add the following code.

var Theater = {
    Models: {},
    Collections: {},
    Views: {},
    Templates:{}
}

This creates a Theater object that includes Models, Colllections and Views object.  This is a standard way of organizing a Backbone application, but not the only way.  I also added a template object.  This is where we will store our html templates; this will be used later.

Since we will be working with movie data, we will create a movie model

Theater.Models.Movie = Backbone.Model.extend({}

This looks fairly simple, but trust me, this is all we need.  We are creating a model class that extends the Backbone.Model.  We could add code within the {}, but for now we don’t need to.

We will be working with a collection of movies, so below we will create a collection class called Movies.

Theater.Collections.Movies = Backbone.Collection.extend({
    model: Theater.Models.Movie,
    url: "scripts/data/movies.json",
    initialize: function(){
        console.log("Movies initialize")
    }
});

This class is created by extending Backbone.Collection.  In the code we identify that the model will be based on the Movie model we created earlier.  Based on my experience with Backbone, initialize will be call anytime a new Backbone object is created.

The url identifies where data should be retrieved from.  Usually this would access the server, but for our simple example we will be access a file that includes the json data.

When the Movies object get created (see below) the initialize function will execute automatically by backbone. We could do this without routes, but I believe this show a more complete example.

When Theater.htm loads main.js, something needs to start the application and this is where routes are used.

Theater.Router = Backbone.Router.extend({
    routes: {
        "": "defaultRoute" //http://localhost:22257/Theater/theater.htm
    },
 
    defaultRoute: function () {
        console.log("defaultRoute");
        var movies = new Theater.Collections.Movies()
        movies.fetch();
        console.log(movies.length)
    }
})

var appRouter = new Theater.Router();
Backbone.history.start();

When a user navigates to the Theater.htm page, the Router will identify where the application will start.  In this example there is a route that identifies what method (defaultRoute) to call when the page loads.

In the defaultRoute method we call console.log so that we can confirm that the route is working correctly.

Next, we create a new Movies object.  And we call movies.fetch().  Based on the url that was identified in Theater.Collections.Movies, when movies.fetch is call a request to movies.json will be executed.  Fetch does an asynchronous call.  So when we call console.log(movies.length) the value displayed should be 0.

To start the application, a Router object is created and Backbone.history.start is executed.  When start is call the appRouter will be executed.

No if we navigate to the page everything should work.

I’m testing my application in Chrome. Below is an image of the results of console.log and how the files are loaded.

In the previous image we can see that the defaultRoute did get executed. The Movies object did get created, and since the fetch is asynchronous the results were not available and 0 was displayed.

In the Network image above we can see the JavaScript files load, and as expected the movies.json file didn’t load until the fetch was called.

In the development tools for Chrome, we can see that the application is working, but nothing is displayed to the user.

I believe there are some important concepts to understand and will not display movies unit the very end.

Lets turn our attention back to the main.js file.

Add the following code after the location Theater.Collections.Movies” is created but before routes.  The following view is extremely over simplified.  Hopefully this will help clarify how a view works. I promise, we will display the movies.

Theater.Views.Movies = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, "render");
         this.collection.bind("reset", this.render);
    },
 
    render: function(){
        console.log("render")  
        console.log(this.collection.length);
    }
})

When the Theater.Views.Movies (describe below) is create, the first method called is initialize.  Initialize is used to setup the View.

Since the view’s methods, such as render, will be execute out of context, we need to make sure that “this” always reference the current view context.  In the render function we reference “this”. If we did not include “_.bindAll(this, “render”);” in initialize then “this” in the render function would not be valid.

Next is probably one of the more important lines, and in the context could be one of the more confusing lines up to this point the object “this.collections” has not been seen.  When looking at this code, “this.collections” has not been defined. Latter we will create a Theater.Views.Movie object.  In the future when we create this view, a collection will be passed to the constructor (see following code).  This will create the view and set the collection to the movies collection.

new Mhs.Views.Movies({collection: movies});

This seem like magic to me and is confusing, but it works.  To help make this more understandable, you could add the following line before initialize, but it’s not needed.

collection: new Theater.Collections.Movies(),

Sorry for the sidetrack, hopefully that clarified some things.

Now back to “this.collection.bind(“reset”, this.render);” and what it does.  This procedure identifies that whenever the collection entire contents is change then execute this.Render. In the code above, in Route we call movies.fetch();  fetch returns all the values from the data store.  When fetch completes and the collection is updated then this.render is executed.

Now to the render function.   Because of the bind we created earlier, anytime the collection is reloaded the render function will be executed.  In this function we are not doing much, but we will.  We are writing to the log that we made it to the render function.  We are also writing to the console the length of the collection.  If we did not include “_.bindAll(this, “render”);” in the initialize then the collection.length would not be valid.

We could refresh the browser, but since the view has not been created, nothing should be different.  But this would be a good time to confirm there are not bugs.

Now that we have the view defined, we now need to create the view.  Add the following line to the defaultRoute function that we created earlier.

var movies = new Theater.Collections.Movies()
new Theater.Views.Movies({ collection: movies }); //!!!! Add this line
movies.fetch();

The first and third line should already be included the defaultRoute function.  The first line creates the movies collection. The second line creates a Movies View and passes the movies collection, which is empty, to the Movies View. This collection will be assigned to the collection in the view. Remember that in view initialize function that when the entire collection changes, call the view’s render function.  At this point we have not called fetch on the collection, so the view’s render function has not been execute; only the view’s initialize has be ran.

Next we execute the collections fetch by call movies.fetch().  When the fetch completes and the values from the fetch have been assigned to the collection, then the collections event called “reset” will be fired and in-turn the view’s render function will be executed.

This is how views reacts to changes in collections.

So now if you refresh your browser, you will see that the render gets called and that there are items in the collection.  There’s no HTML yet, but we can see things are working.

We are still not to the point of displaying the movies.  I want to show one other thing.  I want to show what happens when we add an additional item to the collection and how the view is notified about the new item.

We need to change some code in defaultRoute function.  In the defaultRoute function, any place where movies (lowercase) is reference change it to Theater.movies.  We need to be able to get a reference to the movie collection from outside of the Theater namespace.  The default Route should be identical to the code below.  If you refresh your browser, everything should work the same.

    defaultRoute: function () {
        console.log("defaultRoute");
        Theater.movies = new Theater.Collections.Movies()
        new Theater.Views.Movies({ collection: Theater.movies }); 
        Theater.movies.fetch();
        console.log(Theater.movies.length)
    }

In your html file, add the last line in the following code segment to the body below

<body>
    <h1>My Theater</h1>
    <input type="button" value="Add Item" id="butAddItem" />

Now in you main.js file at the very bottom add the following code

//This is a hack for demonstration purposes
$("#butAddItem").click(null, function () {
    var movie = new Theater.Models.Movie(
        {
            "Id": "BVP3s",
            "Name": "Lord of the Rings ",
            "AverageRating": 4.3,
            "ReleaseYear": 2003,
            "Url": "http://www.netflix.com/.....",
            "Rating": "PG-13"
        }
    )
 
    Theater.movies.add(movie);
    console.log(Theater.movies.length)
})

So when the “Add Item” button is click create a new movie and add the movie the Theater.movies collection.  Also display the length of the collections.  When you run this, the only thing that should happen is that the length of the collection should change increment by 1.

We need to tell the view that when an item is added to the collection, the view should be updated.  If the view makes the following changes, add the string “addOne” to the _.bindAll in the initializer:

_.bindAll(this, "render", "addOne")

Add the following line to the initializer:

    addOne: function (model) {
        console.log("addOne")
    }

So your view should look like this

Theater.Views.Movies = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, "render", "addOne");
        this.collection.bind("reset", this.render);
        this.collection.bind("add", this.addOne);
    },
 
    render: function(){
        console.log("render")
        console.log(this.collection.length);
    },
 
    addOne: function (model) {
        console.log("addOne")
    }
})

If you now refresh your browser and click the “Add Item” button, you will see that the view is notified about the new item that was added to the collection.

Now it’s time to display the movies.

First we will edit the HTML file and add the templates and a container.  Add following code after the button

<input type="button" value="Add Item" id="butAddItem" />
    <div id="mainContainer"></div>
    <script type="text/template" id="tmplt-Movies">
        <ul>
        </ul>
    </script>
    <script type="text/template" id="tmplt-Movie">
		    <div>*******************************************************</div>
		    <div><%= Id %> </div>
		    <div><%= Name %> </div>
		    <div><%= AverageRating %> </div>
		    <div><%= ReleaseYear %> </div>
		    <div><%= Url %> </div>
		    <div><%= Rating %> </div>
    </script>

The mainContainer div is where the movies will be displayed. Since scripts are of type “text/template”, the browser will ignore everything in these script tags. The type of “text/template” doesn’t mean anything to the browser, it’s there for developer.  We could have made the type “cool/tmplt”.  It doesn’t matter much what the type is.

Each script does have an id.  Code in the main.js we will use this id to find these templates.

The template “tmplt-Movies” is very basic, this template could include much more, but for simplicity reasons we will just include the ul div. Each movie will be included in the ul div

For “tmplt-Movie” script, each movie in the collection will execute against this template and the results will be added to “tmplt-movie” ul div.  The <%=%> is the syntax for underscore template engine. Each variable in the template will have a matching property for each movie model. This template is fairly simple as it should be, but depending on needs templates could be much more complex.

The code below can replace the view that you currently have.  I will go over this code below.

Theater.Templates.movies = _.template($("#tmplt-Movies").html())
Theater.Views.Movies = Backbone.View.extend({
    el: $("#mainContainer"),
    template: Theater.Templates.movies,
    //collection: new Theater.Collections.Movies(), //Not needed
 
    initialize: function () {
        _.bindAll(this, "render", "addOne", "addAll");
        this.collection.bind("reset", this.render);
        this.collection.bind("add", this.addOne);
    },
 
    render: function () {
        console.log("render")
        console.log(this.collection.length);
        $(this.el).html(this.template());
        this.addAll();
    },
 
    addAll: function () {
        console.log("addAll")
        this.collection.each(this.addOne);
    },
 
    addOne: function (model) {
        console.log("addOne")
        view = new Theater.Views.Movie({ model: model });
        $("ul", this.el).append(view.render());
    }
 
})
 
Theater.Templates.movie = _.template($("#tmplt-Movie").html())
Theater.Views.Movie = Backbone.View.extend({
    tagName: "li",
    template: Theater.Templates.movie,
 
    initialize: function () {
        _.bindAll(this, 'render');
    },
 
    render: function () {
        //return this.template(this.model.toJSON()); 
        
        //Correction 
        return $(this.el).append(this.template(this.model.toJSON())) ;
    }
})

The first thing is to find the template in the DOM for movies by searching for ”tmplt-Movies” and assign it to an object.  This also compiles the template, so we don’t need to compile it again.

Theater.Templates.movies = _.template($("#tmplt-Movies").html())

In Theater.Templates.movies add the following two lines before the initialize function.

el: $("#mainContainer"),
template: Theater.Templates.movies,

Here we assign the mainContainer div that is in the html file to el; this is where all the movies will be written to.   el is a special identifier that Backbone uses.  If el is not included then Backbone assign <div> to el.

Template is used as a shortcut.  This seems standard in most examples I’ve seen.

In the initialize function change the “_.bindAll” to include “addAll”

_.bindAll(this, "render", "addOne", "addAll");

Remember the _.bindAll provides the correct context of “this” in each of the functions listed

In render function add the following two lines after the console.log

$(this.el).html(this.template());
this.addAll();

The first line gets the template and inserts it into the mainContainer which is represented by el

Add the following addAll function after the render function

   addAll: function () {
        console.log("addAll")
        this.collection.each(this.addOne);
    },

The this.collection.each iterates over each item in the collection and calls the addOne function in the view.

In the addOne function add the following two lines after the console.log

view = new Theater.Views.Movie({ model: model });
$("ul", this.el).append(view.render());

The first line creates a new Movie View, which has not been defined yet.  When creating this view a movie model from the collection is passed to the constructor of the Movie View.

The render function of the movie view is executed and the results are added to the ul div in the mainContainer.

Last but not least, create the Movie View.  Add the following code after the Theater.Views.Movies and before Theater.Router

Theater.Templates.movie = _.template($("#tmplt-Movie").html())
Theater.Views.Movie = Backbone.View.extend({
    template: Theater.Templates.movie,
 
    initialize: function () {
        _.bindAll(this, 'render');
    },
 
    render: function () {
        //Correction 
        return $(this.el).append(this.template(this.model.toJSON())) ;
    }
})

We did this for the previous view; the first thing is to find the template in the DOM for “tmplt-Movie” and assign it to an object.  This also compiles the template, so we don’t need to compile it again.

In the definition of  Theater.Views.Movie create shortcut to the template

In initialize, set the context of “this” for the render function.

The render function gets the json of model, pass the json to the template and return the results of the template.  The results will be used by the parent view to be displayed

Now if you refresh the browser this is what you should see.

Now all the movies have been display in the browser

Here’s what the result html looks like

In the html file, if you move the “Add Item” button below the container you can see how new items added to the collection are displayed in the view.

I hope this is helpful to others.

Hopefully in a few days, I will get the code post in its entirety.

Reference:

This entry was posted in Backbone.js, JavaScript, Tutorial and tagged , , , . Bookmark the permalink.

22 Responses to Understanding Backbone.js – Simple Example

  1. bardev says:

    With Backbone.JS 0.5.2 and newer, _.bindAll is not needed. As per the code below, “this” can be added to the collection bind call. The

    //_.bindAll(this, “render”, “addOne”, “addAll”);
    this.collection.bind(“reset”, this.render, this);
    this.collection.bind(“add”, this.addOne, this);

  2. jbcomrocks says:

    Thanks for creating such a complete example and going step by step, had tried other examples, but they didn’t fit my use case.

  3. mikekoro says:

    Hey

    I cant understand the meaning of these:

    this.collection.bind(“reset”, this.render);
    this.collection.bind(“add”, this.addOne);

  4. Lukas Berk says:

    great complete example, appreciated!!

  5. Lucas says:

    I love you Bardev! What a great example! This is the best one I had read!

  6. Drewyn says:

    Thank you so much for making this article! This helped me to understand Backbone and Underscore, and for the first time my little project has actually been displaying data properly!

  7. Thank you!!! Very Well Written. One of the best backbone examples.

  8. dan says:

    I have tried to simply this and have failed, can anyone help

    http://stackoverflow.com/questions/13742561/backbone-data-view-not-showing

    • bardev says:

      It seems that you want to provide a collection to the template, and that the template should loop through the collection and present the values. You can provide a collection to a template, but that’s probably not the best way.

      The primary problem seems that you are using a colleciton where you should be using an model. In the render function you are passing a collection to the template. The template should take Models Json.

      This is where sub views can be used. So you would want a primary view that takes a collection and that primary view will call a subview that will accept a model.

      I did provide an example on jsFiddle.net. It’s somewhat of an hack. Instead of passing a colleciton into the template, I passed an individual item from the collection. This will only render 1 model. Since Routing can be confusing, I went ahead and removed it.
      http://jsfiddle.net/BarDev/gdQMr/5/

      Just this month I did start creating more simple tutorials on Backbone.js. This list of tutorial is located at the bottom of this page.
      http://bardevblog.wordpress.com/2012/12/04/re-learning-backbone-js/

      Hopefully soon, I will have the time create a simple tutorial on rendering colletion.

      I hope this help.

  9. Pingback: Простые вещи » Blog Archive » Backbone.js: самые первые материалы для быстрого старта

  10. Paras Goel says:

    Thank you very much to make available such a good blog.

    I am new in backbone even but after done some research i am able to understand basic things for backbone.

    Your blog and backbone category is very helpful and it contains all aspects which are required to build a backbone application.

    But i am looking some more help like setup this application on machine. I have created same application on machine but while running the URL that is:

    http://localhost:22257/Theater/theater.htm

    then my application not working and getting error like not found. It seems to me some server or anything else required to run this application if any other thing required please help here.if this application runs on my machine then i can understand other parts also of this category.

    Thanks,

    Paras Goel

  11. Jez says:

    Brilliant tutorial – thank you very much!

  12. RamesH says:

    Very nice tutorial – Thanks a lot

  13. SathishKannan says:

    Good tutorial for beginners…Keep updating the same….Thanks

  14. netalex says:

    the line “new Mhs.Views.Movies({collection: movies});” is correct? for what stands for “Mhs”???

  15. netalex says:

    as for Paras Goel i’m confused too from the localhost port 22257 part: wich local host server do u use to set a specific port? i’m used to simply poit chrome to file://[path]/index.html to test webapp (eventually localhost http://localhost:9910// using ripplesites directory for ripple emulator). obviusly your example has a loto of error not downloading files used by file:// protocol.thank you so it make much more sense.

  16. Amol says:

    Great example for beginner….keep it up…!!!

  17. Dan Voyce says:

    Great simple tutorial. Exactly what you need as a starting point! Good work and thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s