Backbone.js Basics: Bringing an App to Life with Events

    Nick Salloum
    Nick Salloum
    Share

    In a previous tutorial, we plunged into the workings of Backbone.js, an MV* JavaScript framework for building applications. Building a small surf shop app (to help us keep track of surf boards in stock), we looked at creating models, grouping model instances into collections, and iterating over collections to create views. We also took it a step further by rendering each of our model instances into its own view, and grouping them together in a parent view. This is where we left off.

    In this tutorial, we’re going to see the importance of that last step, and introduce some controller-type logic into the picture. I touched on controller-type logic and where it belongs in the last article, but let’s just drive the point home before we continue.

    Separation of Concerns

    In an MV* framework, views are typically responsible for the following:

    1. Rendering the UI, and displaying the data retrieved from the database and/or collections to the end user.
    2. Handling user input via events, and somehow communicating those events to the model.

    The “somehow” part is where the controller logic comes in. In Backbone.js, we’re able to bind events to a view during its instantiation. If we wanted to add or remove stock, or delete a model completely, we’re able to do so. We’ll go through how to do this step by step, but for now, just imagine that we have a button in our view that allowed us to remove one item from stock. We can register that event in the view, which is essentially controller logic. When that button is clicked, we communicate with the respective model, and make the necessary updates to it. Changes in models can also trigger events that the view can handle, ultimately re-rendering the view and displaying the correct data.

    So in a nutshell:

    1. A view renders model data, and registers events.
    2. When an event is fired via user input, we can run some kind of callback or function which communicates with the model.
    3. Inside the model, the logic gets performed. In a real world scenario, you’ll probably also be updating a database here.
    4. The model change fires off an event.
    5. The view picks up on that event, and acts accordingly, possibly re-rendering itself.

    With all that in mind, we now need to think about how we’re going to register these events in the first place. Let’s move on to that.

    Using Backbone Events

    According to the Backbone events documentation:

    Events is a module that can be mixed in to any object, giving the object the ability to bind and trigger custom named events.

    There are many ways to handle events in a Backbone application, but we’ll be leveraging two ways:

    1. We’ll use the catalog of built in events in tandem with the listenTo method, which tells an object to listen to a particular event on another object.
    2. We’ll also delegate events using the events hash directly inside the view.

    Let’s first take a look at leveraging the events hash inside the view instances.

    Leveraging the Events Hash

    Here’s a recap of the current code of the SurfboardView from last time:

    var SurfboardView = Backbone.View.extend({
    
      tagName: 'tr',
    
      template: _.template($('#surfboard-template').html()),
    
      render: function() {
        this.$el.html(this.template(this.model.attributes));
        return this;
      }
    
    });

    You’ll notice that each view instance gets wrapped in its own tr element, which is populated from the template we made before. Let’s edit that template a little bit to include a couple of buttons that will allow us to add and remove stock. We’ll also need to update the markup for the table:

    <table class="table">
      <thead>
        <tr>
          <th>Manufacturer</th>
          <th>Model</th>
          <th>Stock</th>
          <th>Add/Remove</th>
        </tr>
      </thead>
      <tbody id="table-body"></tbody>
    </table>
    
    <script type="text/template" id="surfboard-template">
      <td><%= manufacturer %></td>
      <td><%= model %></td>
      <td><%= stock %></td>
      <td>
        <button class="add-one">+1</button>
        <button class="minus-one">-1</button>
      </td>
    </script>

    Each of our view instances should now have two buttons, but they do nothing at the moment. Let’s take a look at the events hash now, and see how we can register clicks on those two buttons. An events hash in Backbone generally looks like this:

    events: {
      'event target': 'callback'
    }

    The event is the actual event, such as click, dblclick, mouseover, etc. The target is the target element, which is found via DOM traversal based on the view’s element. In other words, if we specified a class name, an ID, or a tag, it would look for the first of that type that the lookup matches. This is why I added class names to the buttons in the template. Finally, the callback is the function that gets called when the event is fired.

    So in our case, we can add the following to our SurfboardView view:

    events: {
      'click .add-one': 'addOne',
      'click .minus-one': 'minusOne'
    },
    
    addOne: function(e) {
      e.preventDefault();
      // code to add one to stock here...
    },
    
    minusOne: function(e) {
      e.preventDefault();
      // code to minus one from stock here...
    }

    The only thing missing now is the script to actually update the model.

    Updating the Model

    If you put an alert or console statement inside either the addOne or minusOne functions in the view, and fire the events by clicking the buttons, you’ll see the events in action quickly. That doesn’t really help us just yet though, as we have to communicate this user action back to the model. So far, I’ve just prevented default browser button behaviour. Next up, we need to call a model function (which we still have to write). These two model functions will reside inside the model itself, staying true to our “separation of concerns” ideology. Here’s the updated SurfboardView:

    var SurfboardView = Backbone.View.extend({
    
      tagName: 'tr',
    
      events: {
        'click .add-one': 'addOne',
        'click .minus-one': 'minusOne'
      },
    
      template: _.template($('#surfboard-template').html()),
    
      render: function() {
        this.$el.html(this.template(this.model.attributes));
        return this;
      },
    
      addOne: function(e) {
        e.preventDefault();
        this.model.addOne();
      },
    
      minusOne: function(e) {
        e.preventDefault();
        this.model.minusOne();
      }
    
    });

    Remember, each view instance contains a reference to its corresponding model instance. This is why we’re able to access model functions using this.model.FUNCTION. Back in our Surfboard model though, we need to account for these functions.

    The logic here is simple, as we just need to grab the existing stock number, either add or subtract one from it, and update that model’s stock number. We already looked at how to get an attribute value from a model. Likewise, we can set it. Here’s the updated model code:

    var Surfboard = Backbone.Model.extend({
    
      defaults: {
        manufacturer: '',
        model: '',
        stock: 0
      },
    
      addOne: function() {
        this.set({
          stock: this.get('stock') + 1
        });
        // probably update a database
      },
    
      minusOne: function() {
        this.set({
          stock: this.get('stock') - 1
        });
        // probably update a database
      }
    
    });

    In a real world app, you’ll probably want to update a database here too, but that is beyond the scope of this tutorial.

    Now if you run this, you’ll notice that the events are being fired when we click the buttons, and the corresponding attribute values in the model instances are being updated. Nothing is happening on screen though. Why is that? Well, we’re not re-rendering the associated view yet, so let’s take a look at that.

    Listening for Events on View Instantiation

    Here’s a quick scenario. Imagine that we had a view that had been rendered to the screen already. A user then interacted with that view, which triggered a change in the model. That model then fires off a change event (which we know can get fired from the catalog of events). When that model is changed, we want to re-render the view. How do we do that?

    For starters, let’s remember that we have an initializer function available in our view instance. We can use that to add the listener, like this:

    initialize: function() {
      this.listenTo(this.model, "change", this.render);
    }

    Our view object is now listening for changes on the model object, and when it does indeed change, we’re calling the render function again, which updates the view.

    Guess what? We’re pretty much there, because of the code we’ve written so far. By clicking those buttons inside each of the view instances, we are not only running the associate callback function, but we’re firing off a change event. That means that by inserting this little line of code in our view, we’re able to do whatever we want when that model’s change event fires. Here’s a look at our updated SurfboardView code:

    var SurfboardView = Backbone.View.extend({
    
      tagName: 'tr',
    
      events: {
        'click .add-one': 'addOne',
        'click .minus-one': 'minusOne'
      },
    
      template: _.template($('#surfboard-template').html()),
    
      initialize: function() {
        this.listenTo(this.model, "change", this.render);
      },
    
      render: function() {
        this.$el.html(this.template(this.model.attributes));
        return this;
      },
    
      addOne: function(e) {
        e.preventDefault();
        this.model.addOne();
      },
    
      minusOne: function(e) {
        e.preventDefault();
        this.model.minusOne();
      }
    
    });

    Now, everything should be working, and you should be able to add and minus stock as you with! Here’s the demo and codebase to go along with it:

    See the Pen Backbone.js Basics – Part 4 by SitePoint (@SitePoint) on CodePen.

    Wrap Up

    And that’s a wrap, folks! In this tutorial, we made some great progress with Backbone, and looked into the importance of events and how to use them. We also explored how the communication lines between views and models work, and how we can keep our code organized to make it more future proof and scalable. There’s a whole lot more to Backbone than this though, and the documentation is full of interesting events, methods, and possibilities. If you have any questions or comments, I’d be glad to hear them in the discussion below. Keep exploring and building, and thanks for reading.

    Want more on Backbone?

    SitePoint Premium has a new course on Backbone.js. Join Premium to access it, and the entire SitePoint library of resources!

    Frequently Asked Questions (FAQs) about Backbone.js Events

    What are the key differences between Backbone.js events and other JavaScript events?

    Backbone.js events are a module that can be mixed into any object, giving the object the ability to bind and trigger custom named events. This is different from other JavaScript events, which are typically tied to specific DOM elements and triggered by user interactions or browser actions. Backbone.js events provide a way to manage changes and updates in a more structured and organized manner, making it easier to maintain and debug your code.

    How do I bind a custom event in Backbone.js?

    To bind a custom event in Backbone.js, you can use the on method. This method takes three arguments: the event name, the callback function, and the context. The event name is a string that identifies the event you want to bind. The callback function is the function that will be executed when the event is triggered. The context is the object that the event will be bound to. Here’s an example:

    var object = {};

    _.extend(object, Backbone.Events);

    object.on("alert", function(msg) {
    alert("Triggered " + msg);
    });

    object.trigger("alert", "an event");

    How can I unbind events in Backbone.js?

    You can unbind events in Backbone.js using the off method. This method removes one or many callbacks. If no context is specified, all of the versions of the callback with different contexts will be removed. If no callback is specified, all callbacks for the event will be removed. If no event is specified, callbacks for all events will be removed. Here’s an example:

    var object = {};

    _.extend(object, Backbone.Events);

    var callback = function(msg) {
    alert('Triggered ' + msg);
    };

    object.on('alert', callback);

    object.trigger('alert', 'an event');

    object.off('alert');

    object.trigger('alert', 'nothing happens');

    What is the use of the listenTo method in Backbone.js?

    The listenTo method in Backbone.js is used to tell an object to listen to a particular event on another object. This is useful in cases where you want to ensure that the listener gets cleaned up correctly. The advantage of using listenTo over on is that listenTo allows the object to keep track of the events, and they can be removed all at once later on. Here’s an example:

    var object = {};
    var otherObject = {};

    _.extend(object, Backbone.Events);
    _.extend(otherObject, Backbone.Events);

    object.listenTo(otherObject, 'alert', function(msg) {
    alert('Triggered ' + msg);
    });

    otherObject.trigger('alert', 'an event');

    How can I trigger an event in Backbone.js?

    You can trigger an event in Backbone.js using the trigger method. This method triggers callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks. Here’s an example:

    var object = {};

    _.extend(object, Backbone.Events);

    object.on('alert', function(msg) {
    alert('Triggered ' + msg);
    });

    object.trigger('alert', 'an event');

    What is the purpose of the once method in Backbone.js?

    The once method in Backbone.js is similar to on, but it causes the bound callback to fire only once before being removed. This is useful for events that should only happen once, like initialization. Here’s an example:

    var object = {};

    _.extend(object, Backbone.Events);

    object.once('alert', function(msg) {
    alert('Triggered ' + msg);
    });

    object.trigger('alert', 'an event');
    object.trigger('alert', 'nothing happens');

    How can I stop listening to events in Backbone.js?

    You can stop listening to events in Backbone.js using the stopListening method. This method removes a previously-bound callback function from an object. If no context is specified, all of the versions of the callback with different contexts will be removed. If no callback is specified, all callbacks for the event will be removed. If no event is specified, all callbacks for all events will be removed. Here’s an example:

    var object = {};
    var otherObject = {};

    _.extend(object, Backbone.Events);
    _.extend(otherObject, Backbone.Events);

    object.listenTo(otherObject, 'alert', function(msg) {
    alert('Triggered ' + msg);
    });

    otherObject.trigger('alert', 'an event');

    object.stopListening();

    otherObject.trigger('alert', 'nothing happens');

    Can I bind multiple events to the same callback in Backbone.js?

    Yes, you can bind multiple events to the same callback in Backbone.js. You just need to provide a space-separated list of event names. Here’s an example:

    var object = {};

    _.extend(object, Backbone.Events);

    object.on('alert warning', function(msg) {
    alert('Triggered ' + msg);
    });

    object.trigger('alert', 'an alert event');
    object.trigger('warning', 'a warning event');

    How can I bind events to changes in a specific attribute of a model in Backbone.js?

    You can bind events to changes in a specific attribute of a model in Backbone.js using the change:attribute event. This event is triggered whenever a specific attribute has been updated. Here’s an example:

    var Book = Backbone.Model.extend({
    defaults: {
    title: 'Unknown'
    }
    });

    var myBook = new Book();

    myBook.on('change:title', function() {
    alert('Title has been changed');
    });

    myBook.set('title', 'New Title');

    Can I bind events to a collection in Backbone.js?

    Yes, you can bind events to a collection in Backbone.js. Collections in Backbone.js have a number of built-in events such as add, remove, and reset. You can also trigger your own events on collections. Here’s an example:

    var Books = Backbone.Collection.extend({
    model: Book
    });

    var myBooks = new Books();

    myBooks.on('add', function(book) {
    alert('A new book has been added');
    });

    myBooks.add(new Book({ title: 'New Book' }));