Publish/subscribe

Backbone classes often wind up being tightly coupled together. For instance, a View class might listen to a Model class for changes in its data, and then, when this data changes, it might look at the attributes of the Model to determine what to render. This practice couples the View class to the Model class, which normally is a good thing as it lets you define the exact relationship you need between the two classes, while still keeping your code fairly simple and maintainable.

When you only have a few Models and Views, it's easy enough to manage their relationships in this way. However, if you are building a particularly complex user interface, then this same coupling can instead become a hindrance. Imagine having a single page with a large number of Collections, Models, and Views, all listening and responding to changes in one another. Whenever a single change occurs, it can cause a ripple effect, resulting in further changes, which can then result in still further changes, and these changes can … well, you get the idea! This can make it nearly impossible to understand what changes are occurring and what effect they will have; in the worst case scenario, they can create infinite loops or make it difficult to fix bugs.

The solution for a complex system like this is to decouple the various components (Collections, Models, and Views) involved with each other by using the Publish/Subscribe pattern (or pub/sub for short). In this pattern, all of the components still communicate via events, but instead of each Collection and Model class having its own event bus, all of the Collection and Model classes involved share a single, global event bus, eliminating direct connections between them. Whenever one component wants to communicate with another, it publishes (triggers) an event that the other component has subscribed (listened) to.

To implement this pattern in Backbone, we need a global event bus, and it turns out that Backbone already includes one for us: the Backbone object itself. Like Models and Collections, the Backbone object has both an "on" (subscription) method and a "trigger" (publishing) method. Both these methods work the same way as they do on other Backbone objects.

One type of application that often benefits from the pub/sub pattern is games, because they often have many different UI pieces being updated by a variety of different data sources. Let's imagine that we're building a three-player game where each player is represented by a Model class. Since we want to keep the player Models updated with changes from the server, we fetch those Model at periodic intervals:

var Player = Backbone.Model.extend();
var bob = new Player({name: 'Robert', score: 2});
var jose = new Player({name: 'Jose', score: 7});
var sep = new Player({name: 'Sepehr', score: 4});
window.setInterval(1000, function() {
    bob.fetch();
    jose.fetch();
    sep.fetch();
});

Let's further imagine that we have a scoreboard View, which needs to update itself in response to changes in the players' scores. Now, of course, in the real world, if we just had a single View, we most likely wouldn't even need the pub/sub pattern, but let's keep this example as simple as possible. To avoid coupling our View to our player Models directly we'll instead have it listen for a scoreChange event on the Backbone object:

var Scoreboard = Backbone.View.extend({
    renderScore: function(player, score) {
        this.$('input[name="' + player + '"]').html(score);
    });
});
var scoreboard = new Scoreboard();
Backbone.on('scoreChange', scoreboard.renderScore, scoreboard);

Now, in order to have these scoreChange events occur, we'll need to make our Player class trigger them. We'll also need a way to tell if a score has changed, but luckily for us, each of the Model classes has a hasChanged method, which does exactly that. We can use this method, inside an overwritten fetch method, to trigger our scoreChange event as follows:

Player = Backbone.Model.extend({
    fetch: function() {
        var promise = Backbone.Model.prototype.fetch.apply(
                          this, arguments
                      );
        promise.done(function() {
            if (this.hasChanged('score')) {
                Backbone.trigger('scoreChange', this.get('name'), 
                                 this.get('score'));
            }
        });
        return promise;
   }
});

As you can see, the Model class and the View are now completely separate. The Model class passes any information that the View requires through the event itself, when it makes the Backbone.trigger call; otherwise, all classes involved remain oblivious to one another.

Of course, as with any of these advanced techniques, there are also disadvantages to this approach, primarily the lack of encapsulation. When you have a View fetch a Model class directly, you know exactly what code connects the two, and if you want to refactor or otherwise change that code, it's easy to determine what will be affected. However, with the pub/sub pattern, it can be much more difficult to determine what code will be impacted by a potential change.

In many ways, this is very similar to the trade-off between using global variables and using local variables: While the former are much more powerful and easy to work with, initially, their very lack of limits can make them much harder to work with in the long run. Therefore, as much as possible, you should avoid relying on the pub/sub pattern and instead, rely on event listeners bound to particular Backbone objects. However, when you do have a problem that requires communication between many disparate parts of your code, relying on this pattern and listening for/triggering events on the Backbone object itself can allow for a much more elegant solution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.104.230