Binding A Backbone View To A Model, To Enable and Disable A Button

Continuing the contrived example from yesterday – a very simple login form – I wanted to show how Joey and I are using backbone’s events and models to enable and disable a button on the form.

 

The Form Layout And Functionality

It’s a very simple form. It has a username and password text box, and a button. What I want to to is make the button disabled initially, and then enable it when a username and password have been supplied.

Here’s the form HTML and layout:

<form action="/login" id="login-form">
  Username: <input type="text" id="username"><br>
  Password: <input type="password" id="password"><br>
  <button id="login" disabled="true">Login</button>
</form>

 

Screen shot 2011 06 15 at 1 13 39 PM

 

The Credentials Model

We need something on the credentials model for the view to pay attention to, to know when to enable and disable the button. A “validated” event would be a great way to listen to the model’s changes for this purpose. Then, if the model is valid we can enable the button and if the model is invalid, we can disable the button.

var Credentials = Backbone.Model.extend({

  initialize: function(){
    this.bind("change", this.attributesChanged);
  },

  attributesChanged: function(){
    var valid = false;
    if (this.get('username') && this.get('password'))
      valid = true;
    this.trigger("validated", valid);
  }
  
});

 

In this code, we are setting up a “changed” even listener in the model’s initializer. The “changed” even fired any time an attribute on the model is being changed. For example, calling “model.set({something: “whatever”})” will change the model’s “something” attribute and cause the change event to fire. Within our change even handler, we are checking to see if the model has both a username and a password. If both are present, the model is valid. If one or both are missing, the model is not valid. We are then raising (triggering) the “validated” event and passing along the knowledge of whether or not the model is valid.

 

The Login View

I’ve updated this a little from yesterday’s example and I’m using backbone’s built in event system instead JQuery’s for binding the username and password inputs. I’ve also added few lines to the initializer to bind to the model’s “validated” event and store the view on the model.

var LoginView = Backbone.View.extend({
  el: "#login-form",

  events: {
    "click #login": "performLogin",
    "change #username": "setUsername",
    "change #password": "setPassword"
  },

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

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

  setUsername: function(e){
    this.model.set({username: this.username.val()});
  },

  setPassword: function(e){
    this.model.set({password: this.password.val()});
  },

  performLogin: function(){
    var user= this.model.get('username');
    var pword = this.model.get('password');
    alert("You logged in as " + user + " and a password of " + pword);
    return false;
  }
});

 

Line 15 binds the view’s validated method to the model’s validated event. This gives us a way to have the view pay attention to the model so that we can change the login button’s disabled attribute. Line 14 is also assigning the view to the model’s .view attribute so that we can access the view in the validated event handler.

The validated function receives the valid argument to tell us whether or not the model is valid. If it is valid, we remove the “disabled” attribute. If it’s not valid, we add the “disabled” attribute.

Notice that the validated function is calling this.view.loginbutton to get to the login button. I’m doing this because “this” in the context of the event handler method is the object that raised the event. In this case, the object that raised the event is the model. I need the loginButton attribute from the view, though, so I have attached the view to the model in line 14 which makes “this.view” available in the validated method.

Honestly, this part confused me. I thought the context of “this” in the validated method was going to be the view itself, not the model. Can anyone explain why it’s the model and not the view?

 

Putting It All Together

Now that our model has a validated event and our view is binding to it, we can enter a username and password to enable the login button:

Screen shot 2011 06 15 at 2 02 32 PM

and when we click on the login button, we get a pop up message telling us the username and password:

Screen shot 2011 06 15 at 2 03 24 PM

If you’re interested in seeing all of the code for this example, head over to the gist and grab the last 2 files. One is a javascript file and the other is the html file, with all of this running.

 

A Small Problem: Blur vs Change

There is a small problem with this example, though. You have to cause the username and password fields to lose focus before the changed events will fire and populate the model with the data. So, as you are typing into the password box (after setting a username), the login button is still disabled. Once you tab or click out of the password box, the button enables.

I thought the change even was supposed to fire for every keystroke that happened in an input box, while the blue event only fired on blur of the input. Can anyone clarify this for me or tell me what I’m doing wrong / how to fix it?

 

Is There A Better Way With Backbone?

I have this sneaking suspicion that there’s a better, more declarative way to bind the view and model together, leading to less code than my example shows. If anyone knows how to get this done in a better way, please share!

 

Still A Contrived Example

One of the commenters on my previous post was asking why you would want to use backbone over JQuery for such simple functionality. In truth, I probably wouldn’t use backbone for such a small piece of functionality in a production application. You could accomplish the same thing with JQuery alone, in much less code. I realize that the examples I’m giving are contrived and not very real-world. They are just examples, though, and would hopefully spark a little more interest or help someone else find that “AHA!” moment on when, where and why they would want to use something like Backbone.


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

    Cool!

    And yes, it’s a lot more code. But it looks so crispy clean and oop. I think there are definitely cases where this could come in handy.

    Although I too got confused a bit with the  this in the validated function. But it’s logic I think. Because you’re actually writing something like:

    this.model.validated(function(valid)    if (valid){      this.view.loginButton.removeAttr(“disabled”);    } else {      this.view.loginButton.attr(“disabled”, “true”);    }  });

  • Edwin Castro

    Why would you bind *in the model* a function *in the model* whose responsibility is to compute a valid value and trigger an event meant for the view?

    I think I would put an isValid() function in the model and a validateModel() function in the view. I would bind the view’s validateModel() function to the model’s change event.

    var Credentials = Backbone.Model.extend({  isValid: function(){    return (this.get(‘username’) && this.get(‘password’));  }});  var LoginView = Backbone.View.extend({  …  initialize: function(){    this.username = $(“#username”);    this.password = $(“#password”);    this.loginButton = $(“#login”);    this.model.view = this;    this.model.bind(“change”, this.validateModel);  },  validateModel: function(){    if (this.isValid()){      this.view.loginButton.removeAttr(“disabled”);    } else {      this.view.loginButton.attr(“disabled”, “true”);    }  },  …});
    Of course, this would look just a little different using Underscore’s _.bindAll() function.

    • http://mutedsolutions.com Derick Bailey

      good question…
      my code is effectively doing the same thing as yours. the difference is that my code is not storing “valid” as a state. the only reason i’m not doing that, is because I didn’t need to store it as a state for such a simple example. 

      in my rails apps and other apps that i’ve built, though, i do need to store ‘valid’ as a state and i would likely write the code the way you’ve shown (or something very similar at least).

      so, it’s really just a simple context vs a real-world context. i think your example would be better for a larger (i.e. production-needs) system where the model is being passed around to multiple places, and validity needs to be checked at arbitrary times. I’m only showing a simple, contrived example so i didn’t really need it to work that way.

      • http://twitter.com/erturne Eric Turner

        I personally like the idea of the model generating a validation event, since you may have several different views on the same model, or other models might be interested in it. I would probably only have the model generate a validation event when the state of the model changes from invalid to valid, or valid to invalid to improve the app’s overall performance.

        • http://mutedsolutions.com Derick Bailey

          +1

          with backbone, setting a “valid” attribute via the ‘set’ method would do this automatically: 

          myModel.set({valid: true})

          this will raise a “change:valid” event on the model. if you set the same value multiple times, it won’t raise the event.

  • http://twitter.com/dira_geek_girl Irina Dumitrascu

    This example would be a lot more MVC if you would bind the “validated” method to the view object. That way, you don’t have to set the view reference in the model, and the code in “validated” would be more natural (“this.loginButton” instead of “this.view.loginButton”)

    • Phil

      I agree , 
      this.model.bind(“validated”, this.validated, this); seems to make more sense to me!

    • http://www.trendybiz.com/ Ricky

      soooo MVC yol. XD

  • http://ben.kree.gr/ Benjamin Kreeger

    I should add (about the View code) that…

    I was having the same frustrating problem with ensuring that “this” actually referred to the View in the bound-from-model method (in your case, `this.validate()`. In my case, it was `this.render()`). Updating my backbone.js and underscore.js files to their newest versions fixed this for me, as you can now pass in a context for “this” into the call to `bind()`.

    As such, your amended view code would look something like…

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

  • umar

    how does the view ‘know’ which model it has bound to. specifially, why does this.model know that the model is the Credentials model?

    • http://mutedsolutions.com Derick Bailey

      when you create a view instance, you tell it what model to use:

      new WhateverView({
        model: someModelInstance
      })

      • Renne

        So why don’t you do that?…

  • Neha

    Thanks.. useful tutorial.

  • dsauced

    keyup vs change should change it on keystroke

    events: {
    “click #login”: “performLogin”,
    “keyup #username”: “setUsername”,
    “keyup #password”: “setPassword”
    },

  • jturo

    Great tutorial, I just wanted to say that i just discovered that instead of writing this


    setUsername: function(e){ this.model.set({username: this.username.val()});},

    You could do this


    setUsername: function(e){ this.model.set({username: $(this.el).val()});},