Listening for Changing Events

This is Part Four of the Backbone Reactive Tutorial. For all sections, please view the Table of Contents.

We now have a simple application that allows us to load data in from a server (mocked in our case), display a list of results and then display specified further information based on a mouse click.

We are explicitly triggering an event, listening for it and carrying out a corresponding action based on the triggered event.

But what about events that we don’t manually trigger? What happens when something changes without our interaction but we still need to respond to it?

This is what listenTo() is for.

Note: You can find all the source code to this project on GitHub, this chapter has been tagged ch4-listen

Data and Template Changes

To demonstrate how the listenTo() method is used we are going to include a new property in the listItem model called isRead. This is a very simple boolean property either false if the message hasn’t been viewed or true if it has.

Before we can include this in the application we first need to make some data and template changes.

Within the mock data for the list we must add the isRead property, for example:

{"id": 5, "date": "1357643063000", "subject": "Testing the Backbone Reactive List Page", "from": "Rob", "isRead": false }

Next we should update the HTML for the list, first the header within the ListView:

    var html = '<h1>Backbone Reactive List (from JSON)</h1>' +
      '<p>This is generated from JSON data. It isn\'t very special but ' +
      'there is enough going on to be a useful demo' +
      '<table border="1"><thead>' +
      '<tr><th>Time</th><th>Date</th><th>Subject</th><th>From</th><th>Read</th></tr>' +
      '</thead><tbody></tbody></table>';

Within the actual ListRowView we need to display a more human readable form of the boolean, so instead of true and false, how about yes and no?

  render: function () {
 
    ...
 
    var isRead = 'No';
    if(this.model.get("isRead")) {
      isRead = 'Yes';
    }
 
    // Later I'll be converting this to a template, but for the time
    // being it will suffice
    var html = '<td>' + timeNice + '</td><td>' + dateNice + '</td>' +
      '<td>' + subject + '</td><td>' + from + '</td><td>' + isRead + '</td>';
    this.$el.append(html);
    return this;
  },

As discussed before including the html directly within the Javascript is very bad practice and we will remedy this in a later chapter, but for now it will suffice.

Updating the Model

When we view the message we want to update the isRead property from false to true. This is very simple using the model’s set() method. Probably the easiest place to change this for us when we load the message, after we have fetched the data.

We should update the loadMessage() function that is called when we want to load the message to display. All we have really done below is add the functionality for setting the isRead property to true for the current ListItem model associated with MessageView.

  loadMessage: function (model) {
    var self = this;
    self.model = model;
    self.model.fetch({success: function () {
      self.model.set('isRead', true);
      self.render();
    }});
  },

Updating of the View

Now we are amending a property within the ListItem model so now we want to be able to listen for this change and update the ListRowView associated with this model accordingly.

This too is simple, all we need to do is when initializing the ListRowView listen for changes to the associate model. If there are any changes, re-render the view.

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

This should have the effect of refreshing the particular row, changing the “Read” column from “No” to “Yes”. However, a mistake earlier means that instead of simply re-rendering and replacing the specific row it re-renders and appends the updated row to the HTML. To change we need to stop appending the row to the DOM and instead just set the HTML

Within ListRowView change:

  render: function () {
 
    ...
 
    this.$el.append(html);
    return this;
  },

to:

  render: function () {
 
    ...
 
    this.$el.html(html);
    return this;
  },

and now it should simply replace the HTML, working as expected.

Conclusion

We are really motoring along with Backbone now and utilising some of its most powerful features. From this chapter we can see that it is relatively simple to layer further functionality over the top of a basic application. This is great for trying things out, but also great for building up an application over a number of iterative sprints.

The listenTo() method allows for disperate components of the application to stay updated on the states of each other and respond accordingly. This in itself cuts down on a lot of foo.on(event) type fluff.

So far we have just been tackling a single page application. Next time we move it up a gear to a multi-page app.

Reminder: You can find all the source code to this project on GitHub, this chapter has been tagged ch4-listen