Backbone.js - Given an element, how do I get the view?
I've created a bunch of Backbone.js vi开发者_如何学Pythonews. Each view has an associated element (view.el
).
Given an element on the page — out of context of the view — what would be the best way to get the view for the element?
For example, say some event affects a bunch of elements on a page and I want to call a method on every view associated with the affected elements.
One way would be to assign the view to the element's data, but I'm wondering if I've missed something smarter:
var myview = BackBone.View.extend({
initialize: function(options) {
$(this.el).data('view', this);
...
}
});
(I'm using Backbone with jQuery 1.5.)
I've just written a jQuery plugin for this. It also uses the .data()
method.
Registration:
I have wrapped / proxied the Backbone View setElement
method to attach the required data to the view's $el
property.
Registration is done behind the scenes like so:
$(myViewsEl).backboneView(myView);
Retrieval:
The plugin traverses up the DOM hierarchy (using .closest()
) until it finds an element with the required data entry, i.e a DOM element with an associated view:
var nearestView = $(e.target).backboneView();
In addition, we can specify what type of Backbone View we wish to obtain, continuing up the hierarchy until we find an instance of matching type:
var nearestButtonView = $(e.target).backboneView(ButtonView);
JSFiddle Example:
Can be found here.
Notes:
I hope I am correct in thinking there are no memory leaks involved here; An 'unlink' is performed if setElement
is called a second time round, and since removing a view's element calls .remove()
by default, which destroys all data as well. Let me know if you think differently.
The plugin code:
(function($) {
// Proxy the original Backbone.View setElement method:
// See: http://backbonejs.org/#View-setElement
var backboneSetElementOriginal = Backbone.View.prototype.setElement;
Backbone.View.prototype.setElement = function(element) {
if (this.el != element) {
$(this.el).backboneView('unlink');
}
$(element).backboneView(this);
return backboneSetElementOriginal.apply(this, arguments);
};
// Create a custom selector to search for the presence of a 'backboneView' data entry:
// This avoids a dependency on a data selector plugin...
$.expr[':'].backboneView = function(element, intStackIndex, arrProperties, arrNodeStack) {
return $(element).data('backboneView') !== undefined;
};
// Plugin internal functions:
var registerViewToElement = function($el, view) {
$el.data('backboneView', view);
};
var getClosestViewFromElement = function($el, viewType) {
var ret = null;
viewType = viewType || Backbone.View;
while ($el.length) {
$el = $el.closest(':backboneView');
ret = $el.length ? $el.data('backboneView') : null;
if (ret instanceof viewType) {
break;
}
else {
$el = $el.parent();
}
}
return ret;
};
// Extra methods:
var methods = {
unlink: function($el) {
$el.removeData('backboneView');
}
};
// Plugin:
$.fn.backboneView = function() {
var ret = this;
var args = Array.prototype.slice.call(arguments, 0);
if ($.isFunction(methods[args[0]])) {
methods[args[0]](this);
}
else if (args[0] && args[0] instanceof Backbone.View) {
registerViewToElement(this.first(), args[0]);
}
else {
ret = getClosestViewFromElement(this.first(), args[0]);
}
return ret;
}
})(jQuery);
Every view can register for DOM events. As such, every view with the kind of element that you are interested in should register for the DOM event and then assign an event-responding function that does what you want. If you need to DRY things up, use mixin techniques to mix in the function.
I think maybe this solution is easier than you may have initially imagined. Just let the views do the work that they are intended to do.
You could maintain a views hash (dictionary) that uses the element as the key and returns the view (or views).
http://www.timdown.co.uk/jshashtable/
I've been using a method inspired by Ed's solution but it does not require the use of jQuery. It does two things:
It sets the attribute
data-backbone-view
on the root elements of all views. This is convenient when looking at the DOM tree in a debugger. You can immediately see which elements are associated with views. You can also use the CSS selector[data-backbone-view]
to find elements that are the roots of Backbone views.It adds a
backboneView
property to each root element of a view. It is then possible to get from the DOM element to the view by looking a the DOM element's properties.
I turn this on only when I'm debugging. Here is the code:
var originalSetElement = Bb.View.prototype.setElement;
Bb.View.prototype.setElement = function setElement(element) {
if (this.el && this.el !== element) {
delete this.el.backboneView;
}
element.backboneView = this;
element.setAttribute("data-backbone-view", "true");
return originalSetElement.apply(this, arguments);
};
Since every view has a reference to the model its displaying, what I would do is assign id of the model to the view's associated element(hopefuly that is not affected by the changes by outside event). Also make sure that the model has a reference to its view. Then have these models stored in a Backbone collection.
With this setup, once something happens to an element, you use the elements id to retrieve corresponding model from Backbone collection that you created above and then this model will give you your view reference.
精彩评论