Backbone.js Application Walkthrough Part 2: Views and Templates – Video Tutorial

Backbone.js Application Walkthrough Part 2: Views and Templates – Video TutorialWe’re moving right along in our video series of tutorials walking you through the process of writing a Backbone.js application. Today we cover the views and embedded templates. There are a number of views here, all pulling from the same models and collections of wine. You’ll see each view, what it’s for, and how it works its magic. Click on through for this long look at Backbone.js views and templates.

Before you watch the video, there a few things I discovered while recording and after recording that I want to bring to your attention. First off, I realized that I forgot to increase the font size of the editor so that it could be more easily seen on small screens, so you’ll definitely want to watch this in full screen on a non-mobile device. Next, there was a line of code that was used a couple times in wine-list.js that I decided to refactor into its own function. Finally, as I mention in the video $(this.el) can be swapped out with this.$el, which is faster because it is already cached. You can see these changes represented in the code given below the video.

Just as a note, after I’m done with this whole project, I will be placing all the code up on GitHub for everyone to download and view.

Backbone.js Video Tutorial Series

HTML Code

<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
    <title>Backbone Cellar</title>
    <link rel="stylesheet" href="css/styles.css" />
</head>
<body>
    
    <div id="header"></div>
    
    <div id="sidebar"></div>
    
    <div id="content">
        <h2>Welcome to Backbone Cellar</h2>
        <p>This is a sample application designed to teach people with the basic knowledge of Backbone.js how to use it in a real application.</p>
    </div>
    
    <script type="text/javascript" src="js/libs/jquery-1.7.1.min.js"></script>
    <script type="text/javascript" src="js/libs/underscore-min.js"></script>
    <script type="text/javascript" src="js/libs/backbone-min.js"></script>

    <script src="js/utils.js"></script>
    <script src="js/models/wine-model.js"></script>
    <script src="js/views/header.js"></script>
    <script src="js/views/wine-list.js"></script>
    <script src="js/views/wine-details.js"></script>
    <script src="js/main.js"></script>
    
    
    <script type="text/template" id="header-template">
        <button class="new">New Wine</button>
    </script>
    
    
    <script type="text/template" id="wine-details-template">
        <div class="form-left-col">
            <label>Id:</label>
            <input id="wineId" name="id" type="text" value="<%= id %>" disabled />

            <label>Name:</label>
            <input type="text" id="name" name="name" value="<%= name %>" required/>

            <label>Grapes:</label>
            <input type="text" id="grapes" name="grapes" value="<%= grapes %>"/>

            <label>Country:</label>
            <input type="text" id="country" name="country" value="<%= country %>"/>

            <label>Region:</label>
            <input type="text" id="region" name="region"  value="<%= region %>"/>

            <label>Year:</label>
            <input type="text" id="year" name="year"  value="<%= year %>"/>

            <button class="save">Save</button>
            <button class="delete">Delete</button>
        </div>

        <div class="form-right-col">
            <img height="300" src="images/<%= picture %>"/>
            <label>Notes:</label>
            <textarea id="description" name="description"><%= description %></textarea>
        </div>
    </script>
    
    
    <script type="text/template" id="wine-list-item-template">
        <a href='#wines/<%= id %>'><%= name %></a>
    </script>
    
</body>
</html>

JavaScript Code

Backbone.View.prototype.close = function() {
    console.log( 'Closing view ' + this );

    if ( this.beforeClose ) {
        this.beforeClose();
    }

    this.remove();
    this.unbind();
}
window.HeaderView = Backbone.View.extend({

    initialize: function() {
        this.template = _.template( $('#header-template').html() );
    },

    render: function() {
        this.$el.html( this.template() );

        return this.el;
    },

    events: {
        "click .new" : "newWine"
    },

    newWine: function() {
        app.navigate('wines/new', true);

        return false;
    }
});
window.WineListView = Backbone.View.extend({

    tagName: 'ul',

    initialize: function() {
        this.model.bind( 'reset', this.render, this);        
        this.model.bind( 'add', this.appendNewWine, this);
    },

    render: function() {
        _.each( this.model.models, function( wine ) {
            this.appendNewWine( wine );
        }, this);

        return this.el;
    },

    appendNewWine: function( wine ) {
        this.$el.append(new WineListItemView({model:wine}).render());
    }

});

window.WineListItemView = Backbone.View.extend({

    tagName: 'li',

    initialize: function() {
        this.template = _.template( $('#wine-list-item-template').html() );

        this.model.bind( 'change', this.render(), this);
        this.model.bind( 'destroy', this.close(), this);
    },

    render: function() {
        this.$el.html( this.template( this.model.toJSON()));

        return this.el;
    }

});
window.WineView = Backbone.View.extend({

    initialize: function() {
        this.template = _.template( $('#wine-details-template').html() );

        this.model.bind( 'change', this.render, this);
    },

    render: function() {
        this.$el.html( this.template(this.model.toJSON()));

        return this.el;
    },

    events: {
        'click .save': 'saveWine',
        'click .delete': 'deleteWine',
    },

    saveWine: function() {
        this.model.set({
            name: $('#name').val(),
            grapes: $('#grapes').val(),
            country: $('#country').val(),
            region: $('#region').val(),
            year: $('#year').val(),
            description: $('#description').val()
        });

        if ( this.model.isNew() ) {
            var self = this;

            app.wineList.create( this.model, {
                success: function() {
                    app.navigate( 'wines/' + self.model.id, false);
                }
            });

        } else {
            this.model.save();
        }

        return false;
    },

    deleteWine: function() {
        this.model.destroy({
            success: function() {
                alert('Wine was deleted successfully');
                window.history.back();
            }
        });

        return false;
    }

});

Backbone.js Video Tutorial Series

Wrapping Up

I hope you all are learning. I know this isn’t exactly the way that someone would proceed through making a web app, but this is a logical way of showing things to you – though, not the only logical way. I’m looking forward to the next piece of this puzzle, as I hope you are also. God bless and Happy Coding!

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Simon Boudrias

    Hey Jim,

    Just tough I’d share some tricks here. Instead of using `$(this.el)`, you can just use `this.$el`. It does the same, but it’s cached by Backbone, so faster to use.

    Also, instead of calling `app.navigate()`, you can most of the time only use a a tag with a href pointing to your routes like `href=”#wines/new”`. This will reduce your code, and it’s mostly the way routes are ment to be use. `.navigate()` function is mostly to redirect a user to a different route without him interacting directly with your interface.

    Anyway, thanks for the video and good continuation.

    • http://www.joezimjs.com Joe Zimmerman

      For your first point, I already mentioned that in the article before the video.

      For your second point, you’re correct, and that’s exactly how the WineListItemView links work, however, you should not use a route for saves/deletes because if someone hits the back button, then it will try to save/delete again. Instead, you use events which trigger a view function to save/delete the model(s). Then, you use app.navigate in order to show results. This article rants pretty well about that: lostechies.com/derickbailey/2011/08/03/stop-using-backbone-as-if-it-were-a-stateless-web-server/

      • Simon Boudrias

        Hi, thanks for your return. Although, I’m not sure I understand your answer concerning the routes. Using a link href or `app.navigate()` is mostly the same, and both fire a routes. And the article you link to is predicating against using routes to launch “action”, which seems pretty logic, and which you’re right, you don’t use in the present context.

        I was only refering to line 18 of `js/views/header.js`, wich should only use an href attributes instead of an event handler to fire a routes. Because each do the exact same thing, except `app.navigate()` solution use more code and “processing power”.

        • http://www.joezimjs.com Joe Zimmerman

          Yea, I didn’t realize you were talking about the header. The header was built with a button by the original author and I decided to stick with that for no really good reason other than the fact that buttons are generally more associated with application actions than links are. Anyway, yes, it would have been much simpler to just make it a link to #wines/new, but in the case of a button we didn’t have that luxury.

  • Ted Jenkins

    Joe, in the code you have posted beneath the video, in the header.js file, your events config object is spelled “evemts”. Just thought you’d like to know.

    • http://www.joezimjs.com Joe Zimmerman

      Thanks very much. It’s the same way in the code I wrote in the video, so thanks to you, we just avoided a potentially difficult-to-detect error from popping up in the next video.

  • stephen

    I believe there is a typo on line line 46 of the wine-details.js…should be”:” not “=”??

    • http://www.joezimjs.com Joe Zimmerman

      Correct. Thank you.