Rendering Backbone (Sub)View

When learning backbone.js (i’m a novice by the way), at first, I was actually having a hard time to grasp the backbone view. Especially how the best practice to render the view and build a rather complex view consists of several subviews. And then, magically I came accross Ian Storm Taylor’s post about his experience on dealing with backbone subview. At first I didn’t quiet understand well about the post until I found a thread on stackoverflow, which was started by Ian Storm Taylor.

Based on the posts, I created a simple case study to better understand how it works. The requirements were to list pairs of username and email input by users. So at first I created the view as listed below:

<div id="application">
  <p>Add a user to the user list</p>
  <div id="login"></div>
  <ul id="online-users"></ul>
</div>
<script id="login-template" type="text/template">
  <h1> login </h1>
  <input id="username" type="text" placeholder="username" />
  <input id = "email" type = "text" placeholder = "email" />
  <button id = "button-login" type = "button"> Login </button>
</script>
<script id="userlist-template" type="text/template">
  <li>
    <span><%= username %> / <%= email %></span>
    <button id = "force-logout">force logout</button>
  </li>
</script>

To better visualize, lets see this picture:

Application view

The outer container is #application and inside it there are two other sub-containers #login and #online-users. Each container or sub-container is represented in a separated view: AppView, LoginView and UserListView respectively. To make it nicely structured, the UserListView has a sub-view called UserView that renders each pair of username and email handles of the button clicked event. there is an exception for the last view where the view is created for each username-password pair then appended in the container #online-users.

Now let’s get to the code.

app.AppView = Backbone.View.extend({
  el: '#application',

  initialize: function () {
    this.users = new app.UserList();

    this.loginView = new app.LoginView();
    this.userListView = new app.UserListView( { collection: this.users } );

    this.listenTo(this.loginView, 'login', this.login);
  },

  render: function () {
    this.loginView.setElement(this.$('#login')).render();
    this.userListView.setElement(this.$('#online-users')).render();
  },

  /* ... */

  login: function (user) {
    this.users.create( user );
  }
});

app.LoginView = Backbone.View.extend({
  template: _.template($('#login-template').html()),

  events: {
    'click #button-login': 'login'
  },

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

  login: function () {
    // login logic
  },

  /* ... */
});

Here, the most important thing to connect Backbone view to the html is the el (element) property. The el property defines to which element the view template will be rendered, or at least in this case study. In the AppView the el is set to #application as the root container of the application. However, for its sub-views, the el property is not defined in the view definition but set dynamically using setElement method. As Ian suggested, this is done to avoid the unbinding of sub-views’ events when rendered more than a time.

The instantiation of sub-views depends on how the sub-views are rendered. LoginView and UserListView are instantiated in the initialize method as the application only need an instance for each of them. In contrast, UserView is instantiated for each user (model) as it needs to associate the button click event inside the view with the contained model.

app.UserListView = Backbone.View.extend({
  initialize: function () {
    this.listenTo(this.collection, 'add', this.renderOne);
    this.listenTo(this.collection, 'reset', this.renderAll);
  },

  render: function () {
    this.renderAll();
  },

  renderOne: function (user) {
    var view = new app.UserView( { model: user } );
    this.$el.append(view.render().el);
  },

  renderAll: function () {
    this.collection.each(this.renderOne);
  }
});

app.UserView = Backbone.View.extend({
  template: _.template($('#userlist-template').html()),
  tagName: 'li',
  events: {
      'click #force-logout': 'clear'
  },

  initialize: function () {
    this.listenTo(this.model, 'destroy', this.remove);
  },

  render: function () {
      this.$el.html( this.template( this.model.attributes ) );
      return this;
  },

  clear: function () {
      this.model.destroy();
  }
});

To make the code structured nicely, I used the html() and append() method of Backbone view’s $el property. Some people might use jquery’s selector $('.element') to render the html, which I think it isn’t clean enough. Lastly, I’ve made a fiddle about this post where you can play around. Hope this post helps someone to understand more about backbone view.

Hello, my name is Nauval. I like building/crafting things. I code for living. I blog in my spare time.

Have a look around and don't hesitate to contact me with any comments, suggestions or even to just say Hi!