Solving Backbone’s “this.model.view” Problem With Underscore.js

In my last post, I briefly touched on a small problem in the relationship between the view and the model. Here’s the relevant code, again:

  var Credentials = Backbone.Model.extend({
    // ... code for a validated event here
  });

  var LoginView = Backbone.View.extend({
    initialize: function(){
      this.loginButton = $("#login");

      // tight coupling and leaky code...
      this.model.view = this;
      
      this.model.bind("validated", this.validated);
    },

    validated: function(valid){
      
      // even more tight coupling between the view and model
      // worse yet, the method, even though it's in the view,
      // executes within the scope of the view!

      if (valid){
        this.loginButton.removeAttr("disabled");
      } else {
        this.loginButton.attr("disabled", "true");
      }
    }
  });

 

This is some awful code, honestly. I didn’t like it when I first wrote it, and I still don’t like it. It coupled my view and my model together in ways that I don’t like. It broke the Law of Demeter. It also confused me – why would the “validated” method of the view execute in the context of the model?! This method is clearly not on the model… but “this” always evaluated as the model, not the view.

 

Understanding This Problem

I’ve read about “this” in javascript a number of times. It represents the current scope of the code that’s executing, similar to “self” in ruby or “this” in C#. Like ruby and c#, javascript has some interesting rules about what “this” evaluates to depending on when and where you are executing code. In effect, “this” can change at times that you wouldn’t expect based on closures, callback methods, and other code that can modify the scope of the executing code.

Look at the following code for some examples of when you might expect “this” to be something other than what it actually is.

$(function(){

  var SomeModel = Backbone.Model.extend({
    raiseIt: function(data){
      this.set({data: data});
      this.trigger("someEvent");
    }
  });

  var SomeView = Backbone.View.extend({
    el: "#input",

    events: { "change #input": "showIt" },

    initialize: function(){
      this.model.bind("someEvent", this.showIt);
      $("#input").change(this.showIt);
    },

    showIt: function(){
      if (this.model)
        alert(this.model.get('data'));
      else
        alert("there is no model attribute!");
    }
  });

  var someModel = new SomeModel();
  var someView = new SomeView({model: someModel});

  // what do you expect the alert boxes to show for these two lines?
  // what do you expect it to show when you change the text input field?
  someModel.raiseIt("foo");
  someView.showIt();

});

 

And here’s the html to run this:

<html>
  <head>
    <script src="jquery-1.6.1.min.js"></script> 
    <script src="underscore-min.js"></script> 
    <script src="backbone-min.js"></script> 
    <script src="example.js"></script>
  </head>
  <body>
    <form action="/login" id="login-form">
      input: <input type="text" id="input">
    </form>
  </body>
</html>

 

Before you continue on to the next images, try to imagine what you’re going to see when you run this code in your browser. Load the page and watch the first two alert boxes go by, then type some text into the input box and tab or click out of it to see the third alert box. Here’s the results, in order:

Screen shot 2011 06 15 at 9 17 36 PM

Screen shot 2011 06 15 at 9 17 49 PM

Screen shot 2011 06 15 at 9 18 11 PM

What did you expect to see in each of the alert boxes? What did you actually see? Personally, I expected all three text boxes to show me “foo”… or maybe the first one should have shown “foo”… but only 1 of the three did – the second call directly to “someView.showIt();” ?! So, what’s going on here? Why is this not behaving the way that you would expect? The answer is in the scope of “this”.

When the first call to someModel.raiseIt(“foo”); is executed, the event “someEvent” is raised. The view has bound to it’s showIt method as an event handler for this event, so the showIt method is called. However, the method is not called by the view. It’s called by the model object. Notice on line 16 of the sample code that the event binding is happening by calling the model’s bind method. We’re effectively telling the model “when ‘someEvent’ fires, call the showIt method from the view class”. This is a closure in action, combined with a function pointer, to make it all work. The end result is that when showIt does execute, “this” is not the view, it’s the model. Since “this” is the model, “this.model” is comes back as undefined, though i would have expected it to come back as the model that is attached to the view.

When the next call to someView.showIt(); is executed, the message box shows us the “foo” data that the previous line had supplied to the model. This really truly baffled me when I first ran my own example code – no joke. I thought I had set something up wrong. As it turns out, it’s correct. Since we are calling someView.showIt(); directly on the view, the scope of “this” evaluates to the view which means “this.model” evaluates to the model correctly. Since we had previously set the model’s “data” to “foo”, evaluating “this.model.get(‘data’)” correctly gives us the value of “foo”.

You may be able to figure out what’s happening with the input box text change, by now. We’re using JQuery to bind to the text box’s change event on 17 of the example code. When you type something into the text box and tab or click out of it, JQuery is firing the showIt method for us. JQuery, as you may have guessed, provides it’s own scope for the method’s execution – the selected element. So, when JQuery fires the event, the scope of “this” is the text box itself, which means “this.model” will be undefined.

But there’s hope!

 

Underscore.js To The Rescue!

Backbone is built with the Underscore.js library. This little library provides a lot of functionality for Backbone’s models, collections, view, controllers, etc. One of the things that it provides is the “_.bindAll” method, which as it turns out, is our savior to make the showIt method behave consistently.

The Backbone documentation has a brief discussion on Binding “this”. It’s worth reading this section to see what Backbone has to say. The gist of it is that “_.bindAll” will allow you to change the context in which a method is fired, so that the scope of “this” is changed to any object you want. What this means for us, is that we can tell our view to always execute shotIt with “this” scoped to the view! Best of all, it’s a one line change:

    initialize: function(){
      _.bindAll(this, "showIt");
      this.model.bind("someEvent", this.showIt);
      $("#input").change(this.showIt);
    },

 

Line 2 was added to the initialize method of our view, to bind the view as the context in which showIt is executed. This gives us the view as “this” in the showIt method, and our problems are magically solved! Now when we execute the script again, we get the following results:

Screen shot 2011 06 15 at 9 42 01 PM

Screen shot 2011 06 15 at 9 42 14 PM

Screen shot 2011 06 15 at 9 42 32 PM

These results finally make sense!

The first call sets the model’s data to “foo”, which is then shown to us. The second call doesn’t change the data, it just calls the showIt method directly, which shows us “foo” again. The third call – facilitated by the change event from the input box – doesn’t change the model’s data either, so when showIt it called for a third time, “foo” is shown to us for a third time.

 

Fixing this.model.view and this.view.loginButton

Now that we understand what’s really going on, we can finally get back to the original login example and fix it.

    initialize: function(){
      _.bindAll(this, "validated");
      this.username = $("#username");
      this.password = $("#password");
      this.loginButton = $("#login");
      this.model.bind("validated", this.validated);
    },

    validated: function(valid){
      if (valid){
        this.loginButton.removeAttr("disabled");
      } else {
        this.loginButton.attr("disabled", "true");
      }
    }

 

There’s only a couple of changes we made to the view. We added line 2 to bind the view as the validated method’s scope. We removed the line that assigned “this.mode.view = this;”. And then in the validated method, we changed “this.view.loginButton” to “this.loginButton” because “this” is now the view, which has our login button!

Our sample application still works the way we expect, but we’ve now cleaned up the coupling issue between the view and the model, fixed the Law of Demeter violation, and generally made the code more consistent in behavior by ensuring that the validated method always executes in the scope of the view.

 

Update: How _.bindAll Works

Matt Shannon sent me a note via twitter, providing me with a few links that describe how the javascript language lets the _.bindAll method work. Of course, this isn’t a breakdown of the bindAll method itself, but they’re the underlying language features that enable it.

Thanks, Matt!


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, Model-View-Controller. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Beau Lyddon

    Not sure if you’ve seen it but there’s a great javascript video from Alex Russell of Google where he breaks down “this” and many other things. I learned more about the “why” of javascript from this video than I have from just about any other source.
    http://www.google.com/events/io/2011/sessions/learning-to-love-javascript.html

    • Kaha

      thanks for sharing, I will definitely will see that video :)

  • Anonymous

    Derick, great write up.  My lack of bind understanding bit me a few times at the beginning of my working with backbone a few months ago.  And yes, I too feel a little like you regarding the model.view = this; :-)

    I look forward to your other backbone.js posts.

  • Gregg

    This whole example seems like it’s far, far easier to do with Knockout. I’d love to see you write up a side-by-side comparison of the two. I’ve messed around with Backbone but Knockout seems to suit my needs nicely and is (mostly) a cinch to use. Still, I don’t know enough about the two to be able to really figure out when Backbone might be a better fit than Knockout….so KO wins by default because it’s easier to use.

    • http://mutedsolutions.com Derick Bailey

      For simple examples, yes, KO is far easier. I’m still looking into it, though, so I can’t really make a good comparison of where each of the two shines. Hopefully I’ll be able to do that soon, though.

  • http://neeraj.name Neeraj
    • http://mutedsolutions.com Derick Bailey

      yeah, i read that post a couple of days ago. it explains more of the detail quite well

  • http://twitter.com/justinhjohnson Justin Johnson

    Thanks for the write-up. Worked for me in one instance but not another. This works:

    _.bindAll(this, ‘configUpdate’);
    _.bindAll(this, ‘displayQuestion’);
    App.bind(‘route:config’, this.configUpdate);
    App.bind(‘route:question’, this.displayQuestion)

    But this did not:

    _.bindAll(this, ‘displayQuestion’);
    this.collection.bind(‘change:answered’, function(question) { this.displayQuestion(); });

    I imagine because of the anonymous function. Any thoughts on how to avoid var view = this; in the 2nd example?

    Edit: I have nested logic in the 2nd example but stripped it out to simplify the discussion. Maybe I should move all of the logic within the anonymous function to a member of the view, which I could then reference as this.handleAnswer()…

    • http://mutedsolutions.com Derick Bailey

      i typically don’t use anonymous methods when binding to model / collection events, specifically so i can avoid this situation. i prefer to call out to methods on the containing object (view, i’m assuming).

      if you want to keep an anonymous method, though, you can do this:

      this.collection.bind(“change:answered”, _.bind(this, function(question){ this.displayQuestion();}));

      the _.bind(this, …) wrapper does the same thing as _.bindAll, except it takes a function as the 2nd arg and returns a function that is bound to the first parameter as the context.

  • Abc

    Thanks Derick.
    This was exactly what I needed

  • http://twitter.com/ozoesono ozo

    … Been battling with this issue and GBAM!!! You just saved my Day! 2 years after your post