Backbone.js: Object Literals, Views Events, jQuery, and `el`

I’ve seen this question in various forms on StackOverflow, more than a few times:

Why aren’t my view event callbacks are being fired?

or

My view events won’t fire. What am I doing wrong?

or

My view’s `el` is empty? Why? The HTML element is right there on the page.

While there are a number of possible problems that can cause these symptoms, it often comes down to an incorrect understanding of Backbone’s object definition syntax and using jQuery selectors to define the view’s `el`.

A Caveat

Before I get in to the real meat of the post, I want to point something out: this post only applies to the situation where you actually need to assign an `el` in your view, instead of letting the view generate the `el` for you. In the vast majority of situations in a production application, you shouldn’t be assigning an `el` (thanks for reminding me, Jeremy!). One of the exceptions to this is progressive enhancement – when you need to take existing HTML and attach JavaScript functionality to it. Most of the time, though, you should let the view generate the `el` for you, and then append the view to an HTML element via jQuery or some other mechanism.

Nothing. No Errors. No Events Firing. Just … Nothing

Look at this code:

MyView = Backbone.View.extend({
  el: $("#someElement"),

  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
});

along with this HTML:

<div id="someElement">
  <button id="someButton">Click Me</button>
</div>

and try to spot the bug. No, it’s not a syntax error of any kind. This code is perfectly valid and runs without any JavaScript errors being thrown. But, chances are, none of the events are going to fire and you’re never going to see an alert box.

Let’s see that code in a JSFiddle, just to make sure:

So what’s going on here? Why isn’t the view registering the events correctly and calling the event handler callback method? The short answer is:

jQuery’s $ selector was called before the DOM was ready.

To understand how we can solve the problem at hand, we need to understand why the problem happens in the first place.

Backbone Object Definitions

A Backbone object definition consists of a few different parts. There’s the use of the `extend` method on the Backbone construct itself, and there’s the definition that you the developer provide to the construct so that the object will have all of your code and do what you want. At first glance – and coming from a more C-oriented world of C# or Java – this has a tendency to look like a class definition. Don’t be fooled, though. There are no classes in JavaScript. Period. Ever. End of discussion. Here, I’ll say it again:

THERE ARE NO CLASSES IN JAVASCRIPT

Clear enough? Great.

What we have is a clever use of underscore.js’ `extend` method with an object literal being passed in to the extend method call, returning a new instance of a constructor function – a JavaScript function that can be used to construct new object instances.

Since we’re passing an object literal in to the extend method, it can be separated out into a variable and method call. The code above is the equivalent of this:

var myViewDefinition = {
  el: $("#someElement"),

  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
};

MyView = Backbone.View.extend(myViewDefinition);

This is important for a number of reasons.

Object Literal Evaluation

Object literals in JavaScript are also known by a number of other names: hashes, associative arrays, key/value sets, JSON, and probably still other names I can’t think of off-hand. Whatever name you’re using to describe them, though, there is one very important aspect of the key / value syntax that you need to understand:

The values in an object literal are evaluated immediately

The left hand side of the `key: value` pair is a static name. It never gets evaluated. Instead, it becomes the identifier (the “key”) for the value on the right hand side. The value on the right hand side, however, is evaluated immediately, when the JavaScript runtime steps through it.

Look at this example:

myObj = {
  foo: "some value",
  bar: 1 + 1
};

In this code, the key of “foo” is assigned to a literal string: “some value”. “Bar” on the other hand, is assigned to an expression: 1 + 1. The result of this expression is 2, so “bar” is assigned the value of 2. It is not assigned an expression that evaluates to the value of 2 every time you access “bar”. It is literally assigned the value of 2 because the “1 + 1″ expression is executed immediately.

Let’s look at “bar” in another light:

function add(a, b){
  return a + b;
}

myObj = {
  bar: add(1, 1)
}

In this example, I’ve created a function that returns the sum of two numbers. When we assign “bar” to “add(1, 1)”, the result will be 2. Note that we are assigning “bar” to the result of the add function (by calling the function with parenthesis and passing in parameters – we are executing the function). When we examine the value of “bar” we get the literal value of 2 because that’s the result of the function we called.

Because object literal values are evaluated immediately, the key we assign will have a value that represents the evaluated value we have supplied. We can use this to our advantage – and in fact, we already are taking advantage of this when we define our Backbone objects.

Backbone, Object Literals, and jQuery

What’s the first thing you learned when someone showed you jQuery for the first time? Most likely, you saw something like this:

$(document).ready(function(){
  // your code here
});

or

$(function(){
  // your code here
});

Then you put your jQuery selector code inside of the jQuery function or document ready function, and started manipulating the DOM like a boss. The reason we do this, of course, is that we need the DOM to be “ready” for jQuery’s selectors to work. Once it’s ready, we can select all day long and things work fine.

Back to the original problem: “my events are not firing”. Can you spot the problem with this sample code, now?

MyView = Backbone.View.extend({
  el: $("#someElement"),

  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
});

So… jQuery’s document ready function + object literals having their values evaluated immediately. The problem in this sample Backbone code, then, is that we are executing the jQuery selector for our view’s `el` before the DOM is ready.

Now that we understand the actual problem, we can look at some possibilities for solutions.

Options And Deferring Execution

Ultimately, the solution we come up with – no matter what the code looks like – is going to be the same thing: deferred execution of the jQuery selector. We need to wait until after the DOM is loaded to run our jQuery selector, so that our view’s `el` will find the element we specify.

Let The View Generate An `el`. Append It To An Element

As I said in the “caveat”, you generally don’t want to assign an `el` to your view. Instead, you should allow the view to generate the `el` and then attach the view’s `el` to an element in your HTML. This reduces the coupling between your JavaScript and your HTML, allowing you more flexibility and making it easier to update what is going where, without having to tear things apart before putting them back together.

Here is one way of accomplishing that goal:

MyView = Backbone.View.extend({
  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  },

  render: function(){
    var html = "generate some HTML, here";
    $(this.el).html(html);
  }
});

$(function(){
  var myView = new MyView();
  myView.render();
  $("#someElement").html(myView.el);
});

In this example, the view is generating it’s own `el` and then the render method is populating it with additional HTML contents. Then in the DOM ready callback, the view is instantiated and rendered, and the existing HTML element is selected then populated with the contents from the rendered view.

Like I said – do it this way whenever you can. If you do need to assign an `el` to a view, though, you do have.

Let Backbone Do It For You

Backbone is a very smart little library. It handles the vast majority of common scenarios itself. Jordan Glasner reminded me, via twitter, that Backbone can handle the jQuery selector for you:

MyView = Backbone.View.extend({
  el: '#someElement',
  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
});

$(function(){
  var myView = new MyView();
});

There may be times when you specifically want to use a jQuery selector to find your element, though. In this case, you still have options.

Put Everything In the jQuery Function?

The first answer that most people come to is to put everything in the DOM ready / jQuery function:

$(function(){

  MyView = Backbone.View.extend({
    el: $("#someElement"),

    events: {
      "click #someButton": "clicked"
    },

    clicked: function(e){
      e.preventDefault();
      alert("I was clicked!");
    }
  });

});

This works – the jQuery selector will find the element and the events will be wired up correctly. However, this isn’t a very good solution. First off, this isn’t a very good practice for organizational reasons. You should limit the amount of code you have in your DOM ready callbacks. It keeps things clean and easy to understand. More importantly though, delaying the definition of your Backbone objects until the DOM is ready can cause a bit of a performance hit for the end user. The site may appear to be a bit slow to get running, even after the content loads. It would be better to have the Backbone objects already defined and only defer the jQuery selector execution.

View Initializer

While it’s true that the value in a JavaScript object literal is evaluated immediately, that doesn’t mean it’s always executed immediately. Much of what we do when we define a Backbone object is specify a function as the value. We do this with the `render` function, the `initialize` function, and any of our own functions that we need to make our views work correctly.

Functions are a very special thing in JavaScript for many, many reasons. One of those reasons – like in most languages – is that functions are essentially deferred execution. You define a function whenever and wherever you want, and the code inside of that function is not executed until you actually call the function. You are deferring execution until you call the function.

We can take advantage of this deferred execution and move our jQuery selector into a function in our view. One of the common places to do this is the initializer of the view:

MyView = Backbone.View.extend({
  events: {
    "click #someButton": "clicked"
  },

  initialize: function(){
    this.el = $("#someElement");
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
});

$(function(){
  var myView = new MyView();
});

We only had to ensure that we are not creating an instance of this view until after the DOM is ready. That was easy enough, with the DOM ready callback instantiating the view. So, we’ve solved the problem of deferring the jQuery selector, and we’ve kept the DOM ready callback to a minimum. It’s a win-win (most of the time)!

Passing `el` Into The View Constructor

There are time when it doesn’t make sense to specify the jQuery selector directly in your view. I won’t go in to all of the details on when, because I think that’s largely a question of style and preferences (which is a very large discussion on it’s own). I do find myself in a situation where I want to decouple the view from the knowledge of it’s `el`, fairly regularly, though. In this situation all we need to do is specify an `el` when we instantiate the view:

MyView = Backbone.View.extend({
  events: {
    "click #someButton": "clicked"
  },

  clicked: function(e){
    e.preventDefault();
    alert("I was clicked!");
  }
});

$(function(){
  var myView = new MyView({
    el: $("#someElement");
  });
});

In this example, the view itself does not define an `el`. This means that we will get an `el` generated for us and end up with a div by default. If you were to instantiate this view without passing any options to the construction, you would get this result. However, by passing in the `el` option, we are overriding the `el` for that specific instance of the view. This means we can specify a jQuery selector to grab the element we want when we instantiate the view. Since we are instantiating the view in the DOM ready callback, the jQuery selector works fine and we get the functionality we expect.

Other Options

There are still other options, I’m sure, for deferring the execution of the jQuery selector so that our view will be given the correct `el`. We could, for example, assign the `el` to a view object after it has been initialized. In the end, though, we are always deferring the execution of the jQuery selector until the DOM is ready, ensuring the availability of the elements we are looking for.

 


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript, JQuery, JSON. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Peter Rust

    Good article, Derick!

    However, you missed the option that I prefer — and I *think* (I could be wrong) it’s the one encouraged/used by the author of Backbone: having the view construct its own element.

    So the declaration would look like this: “el: $(”)” or (and this is one reason why I think this option is encouraged by Backbone) you can simply provide a “tagName: ‘div’” and/or a “className: ‘myClass’” (the tagName will default to div). In these cases (providing a tagName or className) backbone will create a new element for you, as if you had specified “el: $(”)” or “el: $(”)”.

    This way the view’s element by default is not part of the document, which makes the render much faster because each DOM change doesn’t trigger the browser’s re-flow / re-render. It is the responsibility of whoever created the view to append the view’s element to the document somewhere (usually after calling view.render()). This provides a nice encapsulation — the view is only responsible for rendering itself; whoever created it is responsible for placing it where it needs to go.We used to make views (before we used Backbone, so they weren’t Backbone views) that used global jQuery selectors, and we found that it was difficult to instantiate multiple copies of them for testing because they would attach to the “real” element in the Document. Now we are able to instantiate lots of copies of the views, run different tests on each one and the elements are never inserted into the Document, which keeps the tests running fast and keeps the tests isolated from the actual Document (yes, we should *really* isolate the tests by running them in a “headless” browser (phantomjs) or in a simulated browser environment for Node, but we haven’t gotten there yet).

    Peter Rust
    Developer, Cornerstone Systems

    • http://mutedsolutions.com Derick Bailey

      Yeah – Jeremy reminded me of that, too. :D

      I’ve updated the top of the post with a “caveat” explaining this, and the first option shows an example of doing this, now.

    • Anonymous

      Hey there, thanks for the explanation. I want to ask you something would this work for backbone 0.5.3? that’s the version we’re using, and I’m specifying both tagName and className, the elements are being created and rendered correctly (I do an alert with this.el in the render method and see the markup is as I indicated), and as you said I have a single element view that renders itself and a ‘directory’ view that call the single one and appends the singles to the document ; but still the events are not being triggered, and I don’t have any control of the ready section since Im working as a part of a bigger project and the architecture is basically inamovible . Thanks in advance.

  • http://twitter.com/keroger2k Kyle Rogers

    One of the things I dislike about not specifying ’el’ and letting backbone control its creation is how order matters when dealing with layouts.  Sometimes you have to change the order of your renders() so the CSS works correctly when using floats, etc… It is a minor annoyance, but something that has came up on multiple occasions for me.  

    Great article!

  • Christopher Pappas

    Excellent article.  

  • http://twitter.com/tehsuck Christopher Najewicz

    Honestly, this should be linked on the Backbone.js documentation page. Great article and I wish I had a better handle on this before I started head-first w/ Backbone.

  • Phillip Lackey

    Great article!  But the first batch of code that isn’t supposed to work  actually works (at least for me).  Even in IE.

    • http://mutedsolutions.com Derick Bailey

      it’s hit or miss, and depends on the size and complexity of the html / js involved. you run the risk of the DOM not being available when the JS is evaluated. if you put that JS at the  bottom of the HTML, you’re less likely to have a problem, but it’s still possible. 

      i’d rather not have that risk. :)

      • Phillip Lackey

        Ha, I actually just realized what I did – I loaded all my script tags on the bottom of the main html page and your code was the last one so, like you said,  I guess that’s another way to avoid the problem.  :)

  • Paul Garvin

    Thanks so much for this post. I’m writing my first Backbone app and I’ve been pulling my hair out wondering why nothing was working.

  • Don Mitchell

    We can’t get the id based selector to work (“click #someButton” in your example). When we replace it w/ a class-based selector (“click .someButtonClass”), it works. This seems fairly fundamental but is mystifying us [next step, look at the source code.]

    • Don

      Oops. She told me it worked using the class selector, but I guess she didn’t understand. Mea culpa, typo

  • http://needforair.com/ Stanislas Marion

    > Object literals in JavaScript are also known by a number of other names: hashes, associative arrays, key/value sets, JSON,I really like your articles, but this put me off. It is simply not true that an object is a hash and saying that is very misleading. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ 

    • Eric Barr

      So others can save the time, that article doesn’t cut against Derick’s statement. Rather the article points out that the hash-space in javascript is polluted with some preexisting ‘keys’ and describes how to work around the issues.

  • Janne

    You. Are. My. Hero! :) I’m just a noob practicing backbone and have been struggling with this exact problem for the past hour. This was the best explanation ever and with it I got it fixed.

    Thank you Derick!

  • asaf

    Awesome! You somehow managed to explain something extremely important in simple words. You helped me in a 5 days old issue!

  • Carlitos AR

    Great Article! However, is it just me or it’s just the way it’s supposed to be that when the article refers to an example, the code for is never displayed. Still an awesome article, though.

  • Navin Israni

    AWESOME ARTICLE!! Thanks for the stuff Derrick!! :) Loved it… Been working on EXACTLY the same error since 4 hours now.. finally was able to solve it coz of this article! :) THANK YOU SO MUCH!

    PS: I am using Backbone ver 1.0. But the object.$el.hide(); still doesn’t work. I’m also using the latest Chrome browser v28. Finally, what worked was your solution + $(object.el).hide.. the $ around el doesnt wrap the object into a jquery object.

    Still your solution helped me clear a major hurdle.