Extending Backbone views to configure non-DOM events
Quick post today. One of the things I like about Backbone views is how you configure events. It’s very easy and avoids a lot of boilerplate code. For example, if I have a table and want to listen for clicks on the cells, all I need to do is:
var MyView = Backbone.View.extend({ events: { 'click td': '_onCellClick' }, _onCellClick: function (e) { // do stuff... } });
There are lots of nice things about the way Backbone view events work. However, one limitation is they only apply to DOM events. Often I’ll use Backbone events to abstract away what should happen between views or models. Compare the two examples below. In the first when I update the data, everything is tightly coupled together. In the second, they’re decoupled:
var TightlyCoupledView = Backbone.View.extend({ updateData: function (data) { this.model.set('data', data); this.updateGraph(); }, updateGraph: function () {} });
var LooselyCoupledView = Backbone.View.extend({ initialize: function (options) { this.model.on('change:data', this.updateGraph, this); }, updateData: function (data) { this.model.set('data', data); }, updateGraph: function () {} });
It’s a bit more code, but what’s nice is that if I need to add more things to do when the data changes or have more than one point where I update data, it’s not hard to do:
var MoreComplicatedView = Backbone.View.extend({ initialize: function (options) { this.model.on('change:data', this.updateGraph, this); this.model.on('change:data', this.updateTable, this); }, getDataFromAnotherObject: function (obj) { this.model.set('data', obj.getData()); }, refreshDataFromServer: function (data) { var self = this; $.get('/data/latest', function (data) { self.model.set('data', data); }); }, updateGraph: function () {}, updateTable: function () {} });
That’s all good, but you can see what ends up happening. I have a lot of boiler plate hooking things together — the same way you would with DOM events if it weren’t for the way Backbone allows you to configure them. So what I do is an extended view that all my views inherit from that gives them this functionality:
// Cached regex to split keys for `delegate`. var eventSplitter = /^(\S+)\s*(.*)$/; var ExtendedView = Backbone.View.extend({ delegateViewEvents : function (events) { if (!(events || (events = this.viewEvents))) return; if (_.isFunction(events)) events = events.call(this); this.undelegateViewEvents(); for (var key in events) { var method = this[events[key]]; if (!method) { throw new Error('Event "' + events[key] + '" does not exist'); } var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; if (selector === '') { this.bind(eventName, method, this); } else { this[selector].bind(eventName, method, this); } } }, // Clears all callbacks previously bound to the view with `delegateEvents`. undelegateViewEvents: function(eventName) { this.unbind(eventName); } });
It looks very similar to how Backbone sets up and manages DOM events. And here’s how it’s used in practice:
var MoreComplicatedView = Backbone.View.extend({ viewEvents: { 'change:data model': '_onModelDataChange' }, initialize: function (options) { this.delegateViewEvents(); }, updateGraph: function () {}, updateTable: function () {}, _onModelDataChange: function () { this.updateGraph(); this.updateTable(); } });
It’s simple and mirrors the way you’re used to doing things with DOM events. What do you think? How do you handle this issue?
-
Jason
-
bialecki
-
Floyd May
-
bialecki
-
Daniel Björkander
-
bialecki
-
Daniel Björkander
-
Daniel Björkander
-
bialecki
-
Daniel Björkander