3 Tips For Writing Better Backbone Views

Backbone
One of the good things about Backbone.js is it doesn’t tell us how to do things. It leaves it for us to decide what are the best practices for writing views (or any components).
This is also one of the worst things about Backbone. it makes it almost too easy to take the wrong path, and write views that will be hard to maintain.
The principle we should follow when writing a view is to keep it encapsulated, and keep it as “dumb” as possible – a view should know only the bare minimum it needs to know in order to do it’s job, and do bare minimum it has to do.

Here are 3 simple tips that can help us achieve this:

1. Don’t Access Variables Out Of The View Scope

var View = Backbone.View.extend({
  initialize: function(){
    this.model = window.App.myModel;
    this.model.fetch();
    //...
  }
});

While its tempting to write a view such as the above, it introduces some problems.
The view needs to know about the window.App and it manipulates a model’s state. We have tight coupling.
It violates the single responsibility principle: the view should be responsible only to render itself, but when it accesses the global scope, finds a model and operates a fetch on it, it becomes responsible for application state.
Writing a unit test can quickly show us how coupled it is: we will need to mock window.App, add a mock model to it, make sure it fetches, etc.

A better pattern would be to take care of these in the views parent (controller/route)

var myView = new View({model: window.App.myModel});
window.App.myModel.fetch();

In this example, we are passing a fully baked model, leaving only the rendering job to the view.

2. Write The Template Inline

There are three base techniques for writing and storing backbone/underscore templates:

1. In our main html file, in a script tag:

<!-- index.html -->
<script type="text/template" id="pod">
  <h2><%= title %></h2>
  <p><%= body %></p>
</script>
<script src="view.js"></script>
// view.js
var View = Backbone.View.extend({
  template: _.template($("#pod").html())
});

By using the script tag, we are telling the browser to not render this element.
By choosing a script type that doesn’t have a matching interpreter, we ensure this script will not run.

2. In an external file, loading over network, with dependency management utilities such as requireJS

<!-- pod.html -->
<h2><%= title %></h2>
<p><%= body %></p>
// view.js
define(["text!templates/pod.html"], function(pod) {
  var View = Backbone.View.extend({
    template: _.template(pod)
  });
});

3. In our view declaration, as a string.

// view.js
var View = Backbone.View.extend({
  template: _.template('\
    <h2><%= title %></h2>\
    <p><%= body %></p>\
  ')
});

Option #1 has tight coupling between our view and the main DOM, and I wouldn’t recommend using it.
Option #2 and #3 are both good. I tend to prefer #3 for a few reasons:

  • Its simple. No magic is happening.
  • Everything is in one file, which makes it easy to develop
  • No editor highlights html in a javascript string, so we are “forced” to write short and clean templates.

3. Render UI Upon Data Change, Not User Interaction

The flow is : User interaction -> data change -> view render.
For instance, if we are writing a play/pause toggle button in an audio player, the flow would be:

  1. The user hits ‘play’
  2. The model (data) state changes to ‘playing’
  3. The view renders in ‘playing’ mode

Following this pattern ensures that changes to the state triggered from other sources (such as fast forward, new playlist, network error, etc.) will toggle our button to do the right thing. In other words, we have a single source of truth: whenever our model changes we know we should render our button.

The code can look like:

var PlayPauseButton = Backbone.View.extend({
  tagName: 'li',
  className: 'icon',
  initialize: function(){
    this.model.on('change:status', this.render, this);
  },
  render: function(){
    this.$el.removeClass('icon-play').removeClass('icon-pause');
    this.$el.addClass('icon-' + (this.isPlaying() ? 'pause' : 'play'));
  },
  events: {
    'click': function(){
      this.model.set('status', this.isPlaying() ? 'paused' : 'playing');
    }
  },
  isPlaying: function(){
    return this.moedl.get('status') === 'playing';
  }
});

Conclusions

Keeping decoupled and encapsulated views helps us writing a sane, maintainable application.
Following some best practices makes it very easy to do so.
We all used the lack of time as an excuse to explain the poor quality of our code (at least I did), but in most cases, writing good code doesn’t take more time.

Lead UI Engineer and a Software Architect.
Over 12 years of professional experience, writing complex web applications, and still learning something new every day.
Currently working for Okta in San Francisco

Twitter LinkedIn Google+ 

Leave a Reply

Your email address will not be published. Required fields are marked *

  1. I don’t really approve that models should contain view-specific state data like “isPlaying”. It effectively prevents from sharing the same model between two views, i.e. leads to data duplication and therefore risks data incoherency if instance A of the model changes but instance B doesn’t.

    • Hi Silka,
      A state of the model is a part of the model (eg, if this song is playing, than it is playing in any given view, this is not a view specific state – it’s a model state.
      isPlaying is a convenience method in the view to get *data* from the *model*

      • @Uzi, I completely agree that the model keeps track of the state and the view is simply accessing this state from the model. To that end could you not have the method on the model and just let the view call it:
        Model method:
        function getStatus(){
        return this.get(‘status’) === ‘playing’;
        }

        then on the View:
        function isPlaying(){
        this.model.getStatus()
        }

        But that is a minor quibble! Overall great stuff here!!

        • There are two different concepts, a (Data) Model which is the data that gets stored, which is served by RESTful services, and a ViewModel, which is what frameworks like Angular use. I think it doesn’t make sense to have a _isPlaying_ on a Data Model, but many find it helpful to update a data object and let that update your view, instead of touching the view directly.

        • “Tell, don’t ask”.

          So, on the view:
          this.model.toggleStatus()

          So, on the model:
          if (isPlaying) {
          this.status = ‘paused’;
          } else {
          this.status = ‘playing’;
          }

          this.trigger(‘player:status:changed’, this.status);

          By this way, view will only conduct UI events to the central logic which is the model. The model will handle the rest. With the event mechanism, the view can also update the latest status from the model.

          The author of the blog might update the article with these new information I think.

          • Backbone models trigger `change` events when something changes, so you don’t ask either way. the question is who tells you, and in a backbone app, it’s very common to have a “state” models holding state information that is decoupled from the view layer. React has similar approach of having a central state object.

          • Holding state information is very fine and that was exactly my point. But, what I wanted to tell was that asking for a state is not a good way for decoupling. View should just update and get info from DOM. Better not to ask this kind of trivial state info. Or you will welcome coupling.

  2. Hi Uzi, great post- one quick question though. Regarding your first comment, how far should we extract out the logic from the view? For example I have:

    var TableView = Backbone.View.extend({
    initialize: function(options) {
    this.template = _.template($(options.template).html());
    this.el = options.el;

    this.collection = new WidgetCollection([], { widgetGroup: options.widgetGroup });
    this.collection.bind(“reset”, this.render, this);
    this.collection.bind(“change”, this.render, this);
    this.collection.fetch();
    }
    });
    var _tableView_individuals = new TableView({ widgetGroup: ‘individuals’, el: ‘#individuals-wo-goals-tablelist’, template: ‘#table-row-individuals’ });

    Which I changed to:

    var TableView = Backbone.View.extend({
    initialize: function(options) {
    this.template = _.template($(options.template).html());
    this.el = options.el;

    this.collection = new WidgetCollection([], { widgetGroup: options.widgetGroup });
    this.collection.bind(“reset”, this.render, this);
    this.collection.bind(“change”, this.render, this);
    }
    });
    var _tableView_individuals = new TableView({ widgetGroup: ‘individuals’, el: ‘#individuals-wo-goals-tablelist’, template: ‘#table-row-individuals’ });
    _tableView_individuals.collection.fetch();

    … but should all those bindings and stuff be taken out of the view too? I’m very new to Backbone so I’m a bit confused about a lot of the modelling.

    Many thanks! 🙂
    Matt

  3. Hi Uzi,

    Thanks for this. This has helped me to make my code better. I have gone for option 2. 🙂

    I have one question. For one of my pages, I have a collection and a corresponding template.

    I need to render different items in collection to different part of page (based on one of the properties of item). I know that I need to use different templates. One way to render is, iterate through each item in collection and assign template and render.

    However that would mean multiple refreshes. any better solution you would propose?

    Thanks
    Ajay

  4. Mmmmh, those pieces of suggestions about templating are not that good if you’re developing a SPA! They’re ok for “small views”, though

  5. Can’t agree with storing HTML in strings because, as you say, “lack of syntax highlighting forces us to use shorter templates.” If your views are used in both browser and Node environments, certainly storing templates in tags is not going to work. But if you’re writing client-only code, I fail to see the downside of “coupling to the DOM.”

    IMO, having HTML fragments in strings is a nasty code smell. I’m curious what experiences you’ve had in your career that have led you to that implementation?

    • In a given web app you end up having over 50 views.
      If you want to add all the 50 templates in one html file, you end up with a mess.

      The downside of coupling with the DOM is:
      a. in order to test you need to fill the DOM first.
      b. in order to re-use a view in another page you need to copy DOM elements
      So essentially its:
      a. harder to maintain
      b. harder to test
      c. adds no value

      The beauty is when a typical view in the code base is not longer than 5 lines.