I am learning the Backbone.js JavaScript framework right now, and ran into a problem. Backbone Collections and Models can be set up to asynchronously load data from the server with the fetch()
method, which fires off an Ajax sync
call. You can bind a callback method to a Collection’s reset
event, which Backbone provides. It was my impression that the change
event on a Model would do the same thing. But when I bound to the Model’s change
event in a View, it did not work as expected. Here is the code that was not working:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
NOTE: the .on()
method was formerly called .bind()
apparently, so some older online examples use this.model.bind()
, and this still works, presumably calling the underscore.js bind()
method?
Well, it turns out I was making a simple mistake about one of JavaScript’s trickier points: this
context in the callback function. A substantial amount of reading later (this article in particular was helpful) I realized I needed to pass in the this
context as a third parameter to the on()
method. There is actually a whole section on this
in the Backbone documentation.
More research showed me that since Backbone version 0.9.9 a new method was added to make it more foolproof to bind callbacks with the right context: listenTo()
. This method is on the object that has the callback, instead of on the object you are binding to. This way it assumes the correct context and you don’t need to worry about binding it correctly. Below is the working method of binding to a successful Model load, with the new listenTo()
syntax.
1. Bind callback to Backbone.js Model ‘change’ event
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
But before I figured that out I discovered two other ways to bind callbacks to successful Model and Collection fetch()
Ajax calls. So I’ll share those here as well. Both of them use jQuery functionailty, so if you are using Backbone with anything besides jQuery (like Zepto?), YMMV.
2. Pass ‘success’ callback into to the Model’s ‘fetch()’ method
The second method is to pass a callback via option parameters into the Model’s fetch()
method. fetch()
passes these parameters on to the $.ajax()
method, so you can pass in options that $.ajax()
accepts. This includes the success
callback option. The trick here, as with the first technique, is making sure the callback has the correct this
context. You can do this with the Underscore _.bindAll()
method, which works some magic that is not quite clear to me (although this StackOverflow post helped).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
3. Bind with jQuery Deferred ‘done()’ callback on ‘fetch()’ method
The final method uses jQuery’s Deferred object, which is a powerful tool for making JavaScript more synchronous when you need it to be. I got the idea from this blog. This article is also helpful for understanding Deferred. Basically objects that implement Deferred (like $.ajax()
) return a “promise” object, which takes in callbacks via the done()
method and queues them up. The queue of callbacks is run when the asynchronous method completes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
These last two methods are particularly useful when you only want to bind to the initial Model load event, and not subsequent change
events (i.e. change:attribute_name
) which are fired every time model.set()
is called. For instance maybe you have some initial view rendering you want to do when the model first loads, and then have separate callbacks to update the view when model attributes change.
Here is a JsFiddle of all three Backbone Model fetch() bind methods to play with.
Waiting for multiple models to load with Deferred
Using jQuery Deferreds in Backbone is great for binding single callbacks that depend on multiple Models loading. It is much cleaner than chaining a series of callbacks, and allows the load events to run in parallel. You can easily do multiple-dependency callbacks with the when()
method, like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Here is a running JsFiddle of binding to two Model fetch() calls
Deferred is, as I said, quite powerful and I’m happy to have it in my tool belt now (even though I solved my original Backbone issue without it).
(Want to use Deferred style JS on more than just jQuery AJAX objects, or without jQuery at all? Sam Breed created a plugin/mixin to enable Deferred functionality in Underscore.js. Might be worth checking out if you are battling with asynchronous issues.)