Last active
December 18, 2015 09:29
-
-
Save richardm/5761343 to your computer and use it in GitHub Desktop.
Extending Backbone Views to add global namespaced events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Basic App Setup | |
var App = window.App = { | |
Models: {}, | |
Views: {}, | |
Collections: {}, | |
View: {} // set below | |
}; | |
// Custom App View with global namespaced events | |
(function(){ | |
// Global events handler | |
// It's a private global :-) | |
var GlobalEvents = _.extend({}, Backbone.Events); // could probably just use Backbone.Events | |
// App.View extends Backbone.View to provide global namespaced triggers | |
// Could probably just override Backbone.View | |
App.View = Backbone.View.extend({ | |
constructor: function(options){ | |
var options = _.extend({ns: 'global'}, options); // namespace defaults to global | |
App.View.__super__.constructor.call(this, options); | |
}, | |
triggerGlobal: function(eventName, attrs){ | |
GlobalEvents.trigger(this.options.ns + ':' + eventName, attrs); | |
}, | |
handleGlobal: function(eventName, callback){ | |
this.listenTo(GlobalEvents, this.options.ns + ':' + eventName, callback ); | |
}, | |
// Preserves namespace for sub-views | |
subView: function(viewName, options){ | |
var options = _.extend({ ns: this.options.ns }, options); | |
return new viewName(options); | |
} | |
}); | |
})(); | |
// App components... | |
App.View.Container = App.View.extend({ | |
initialize: function(){ | |
this.handleGlobal('selectItem', this.updateSelectedItem); | |
}, | |
render: function(){ | |
var list = this.subView(App.View.List, { collection: ... }); | |
this.$el.append(list.render().el); | |
}, | |
updateSelectedItem: function(model){ | |
// do something with the model corresponding to the clicked row | |
} | |
}); | |
App.View.List = App.View.extend({ | |
tagName: 'ul', | |
render: function(){ | |
this.collection.each(function(item){ | |
var itemView = this.subView(App.Views.ListItem, { model: item }); | |
this.$el.append(itemView.render().el); | |
}, this); | |
return this; | |
} | |
}); | |
App.View.ListItem = App.View.extend({ | |
tagName: 'li', | |
events: { | |
'click': function(){ this.triggerGlobal('selectItem', this.model); } | |
}, | |
render: function(){ | |
this.$el.html(...); // create list item with values from this.model | |
return this; | |
} | |
}); | |
// App logic | |
var leftPane = new App.View.Container({ ns: 'leftpane' }); | |
// leftPane can now handle events triggered by leftPane -> list -> listitem | |
var rightPane = new App.View.Container({ ns: 'rightpane' }); | |
// rightPane can now handle events triggered by rightPane -> list -> listitem | |
// Why bother? | |
// Clean design | |
// views only have references to immediate children | |
// children have no reference to parent | |
// Clean event handling | |
// triggering event in list item event handler allows us to pass model (cleaner than looking up model) | |
// global events object allows container to handle event (remember it has no reference to list items) | |
// Namespacing | |
// allows lists to trigger events without affecting event handling for other lists | |
// subView method allows sub views to automatically inherit parent's namespace with no clutter | |
// Real World use? | |
// Populate details pane when person is selected from list | |
// Update image preview when thumbnail is selected from list | |
// Reuse List / ListItem to do both with containers handling custom logic |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment