Backbone.Events: Decoupling Your Spaghetti Code with Pub/Sub
It's 2012, and "Single Page Applications" are the new frontier. But as our apps grow, we're finding that managing UI state with just jQuery is leading to a nightmare. View A needs to update View B, which triggers a change in View C. Before you know it, your code is a tangled web of $('.selector').parent().next().find('.other').
Backbone.js provides a clean way out of this "spaghetti" by giving us a robust Event system that implements the Observer (Pub/Sub) pattern.
The Problem: Tight Coupling
Imagine a shopping cart. When you click "Add to Cart" in the ProductView, the CartSummaryView needs to update the total.
// 2011 style (Bad)
var ProductView = {
addToCart: function() {
// Logic to add to cart...
// Tightly coupled: ProductView must know about CartSummaryView
window.cartSummaryView.render();
}
};
If you remove the CartSummaryView or rename its methods, the ProductView breaks.
The Backbone Solution: Pub/Sub
In Backbone, any object can be an event emitter. The Backbone.Events module is mixed into every Model, Collection, and View by default. But you can also use a standalone object as a "Global Event Bus."
// Create a global dispatcher
var vent = _.extend({}, Backbone.Events);
var ProductView = Backbone.View.extend({
events: {
'click .add-button': 'onAdd'
},
onAdd: function() {
// Just trigger the event, don't worry about who is listening
vent.trigger('cart:add', this.model);
}
});
var CartSummaryView = Backbone.View.extend({
initialize: function() {
// Listen for the event
vent.on('cart:add', this.updateTotal, this);
},
updateTotal: function(product) {
console.log('Adding ' + product.get('name') + ' to cart summary');
this.render();
}
});
Advanced Event Logic
Backbone events aren't just for simple triggers. You can pass multiple arguments, and use special events like all to debug your entire application flow.
vent.on("all", function(eventName) {
console.log("Global Event Triggered: " + eventName);
});
One critical feature added recently is listenTo(). Before listenTo(), you had to manually unbind events to prevent memory leaks (the "zombie view" problem).
var MyView = Backbone.View.extend({
initialize: function() {
// If the view is removed, Backbone automatically unbinds this
this.listenTo(vent, 'cart:clear', this.onClear);
},
onClear: function() {
this.$el.empty();
}
});
By leveraging Backbone.Events, we move from a world of "imperative DOM manipulation" to a world of "reactive state changes." It's the only way to build the complex interfaces that 2012 users are starting to expect.
Aunimeda designs and builds scalable software architectures - from system design to implementation and ongoing engineering.
Contact us to discuss architecture for your project. See also: Custom Software Development, Web Development