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?
My view events won’t fire. What am I doing wrong?
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`.
Nothing. No Errors. No Events Firing. Just … Nothing
Look at this code:
along with this HTML:
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
Clear enough? Great.
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:
This is important for a number of reasons.
Object Literal Evaluation
The values in an object literal are evaluated immediately
Look at this example:
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:
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:
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?
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
Here is one way of accomplishing that goal:
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:
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:
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.
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:
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:
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.
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.