-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Various clean up and optimizations #5
Changes from 6 commits
19d82b8
846de91
54252e6
33e23df
8ab8646
31bb428
15cb64e
09d3762
f872032
7c46856
aca58b7
e14a903
be3a354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1031,20 +1031,29 @@ | |
return this; | ||
}, | ||
|
||
// Remove this view by taking the element out of the DOM, and removing any | ||
// applicable Backbone.Events listeners. | ||
// Remove this view by taking the element out of the document, remove all | ||
// the DOM event listeners attached to it, and remove any applicable | ||
// Backbone.Events listeners. | ||
remove: function() { | ||
this.$el.remove(); | ||
this.undelegateEvents(); | ||
this._removeElement(); | ||
this.stopListening(); | ||
return this; | ||
}, | ||
|
||
// Remove this view's element from the document and remove all the event | ||
// listeners attached to it. Useful for subclasses to override in order to | ||
// utilize an alternative DOM manipulation API. | ||
_removeElement: function() { | ||
this.$el.remove(); | ||
return this; | ||
}, | ||
|
||
// Change the view's element (`this.el` property), including event | ||
// re-delegation. | ||
setElement: function(element, delegate) { | ||
if (this.$el) this.undelegateEvents(); | ||
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); | ||
this.el = this.$el[0]; | ||
this.undelegateEvents(); | ||
this._createContext(element); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about if createContext set this.el = this._createContext(this.el); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah. There should be one and only one place that assigns to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's only I don't know about making |
||
if (delegate !== false) this.delegateEvents(); | ||
return this; | ||
}, | ||
|
@@ -1067,49 +1076,57 @@ | |
delegateEvents: function(events) { | ||
if (!(events || (events = _.result(this, 'events')))) return this; | ||
this.undelegateEvents(); | ||
var _delegate = this._delegate; | ||
for (var key in events) { | ||
var method = events[key]; | ||
if (!_.isFunction(method)) method = this[events[key]]; | ||
if (!method) continue; | ||
|
||
var match = key.match(delegateEventSplitter); | ||
var eventName = match[1], selector = match[2]; | ||
this.delegate(eventName, selector, method); | ||
method = method.bind && method.bind(this) || _.bind(method, this); | ||
_delegate.call(this, eventName, selector, method); | ||
} | ||
return this; | ||
}, | ||
|
||
// Add a single event listener to the element responding only to the | ||
// optional `selector` or catches all `eventName` events. Subclasses can | ||
// override this to utilize an alternative DOM event management API. | ||
_delegate: function(eventName, selector, method) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer unprefixed |
||
eventName += '.delegateEvents' + this.cid; | ||
if (!selector) { | ||
this.$el.on(eventName, method); | ||
} else { | ||
this.$el.on(eventName, selector, method); | ||
} | ||
return this; | ||
}, | ||
|
||
// Clears all callbacks previously bound to the view with `delegateEvents`. | ||
// Clears all callbacks previously bound to the view by `delegateEvents`. | ||
// You usually don't need to use this, but may wish to if you have multiple | ||
// Backbone views attached to the same DOM element. | ||
undelegateEvents: function() { | ||
this.$el.off('.delegateEvents' + this.cid); | ||
if (this.$el) this.$el.off('.delegateEvents' + this.cid); | ||
return this; | ||
}, | ||
|
||
// Add a single event listener to the element. | ||
delegate: function(eventName, selector, method) { | ||
if (_.isFunction(selector)) { | ||
method = selector; | ||
selector = undefined; | ||
} | ||
eventName += '.delegateEvents' + this.cid; | ||
if (!selector) { | ||
this.$el.on(eventName, _.bind(method, this)); | ||
// Creates the actual context for this view using the given `root` and a | ||
// hash of `attributes` and returns the created element. `root` can be a CSS | ||
// selector or an HTML string, a jQuery context or and Element. Subclasses | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// can override this to utilize and alternative DOM manipulation API. | ||
_createContext: function(root, attributes) { | ||
var $el; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about the one-liner instead? _createContext: function(el, attrs) {
this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
this.$el.attr(attrs || {});
return this.$el[0];
} |
||
if (typeof root == 'string') { | ||
$el = Backbone.$(root); | ||
} else if (root instanceof Backbone.$) { | ||
$el = root; | ||
} else { | ||
this.$el.on(eventName, selector, _.bind(method, this)); | ||
$el = Backbone.$(root); | ||
} | ||
return this; | ||
}, | ||
|
||
// For hooking into small amounts of DOM Elements, where a full-blown template isn't | ||
// needed, use **make** to manufacture elements, one at a time. | ||
// | ||
// var el = this.make('li', {'class': 'row'}, this.model.escape('title')); | ||
// | ||
make: function(tagName, attributes, content) { | ||
var $el = Backbone.$('<' + tagName + '>', attributes).html(content); | ||
return $el[0]; | ||
if (!_.isEmpty(attributes)) $el.attr(attributes); | ||
this.$el = $el; | ||
return (this.el = $el[0]); | ||
}, | ||
|
||
// Ensure that the View has a DOM element to render into. | ||
|
@@ -1121,7 +1138,8 @@ | |
var attrs = _.extend({}, _.result(this, 'attributes')); | ||
if (this.id) attrs.id = _.result(this, 'id'); | ||
if (this.className) attrs['class'] = _.result(this, 'className'); | ||
this.setElement(this.make(_.result(this, 'tagName'), attrs), false); | ||
var el = document.createElement(_.result(this, 'tagName')); | ||
this.setElement(this._createContext(el, attrs), false); | ||
} else { | ||
this.setElement(_.result(this, 'el'), false); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,20 +26,14 @@ | |
strictEqual(view.$('a b').html(), 'test'); | ||
}); | ||
|
||
test("make", 3, function() { | ||
var div = view.make('div', {id: 'test-div'}, "one two three"); | ||
test("_createContext", 5, function() { | ||
var div = view._createContext('<div>', {id: 'test-div'}); | ||
|
||
equal(div.tagName.toLowerCase(), 'div'); | ||
equal(div.id, 'test-div'); | ||
equal($(div).text(), 'one two three'); | ||
}); | ||
|
||
test("make can take falsy values for content", 2, function() { | ||
var div = view.make('div', {id: 'test-div'}, 0); | ||
equal($(div).text(), '0'); | ||
|
||
div = view.make('div', {id: 'test-div'}, ''); | ||
equal($(div).text(), ''); | ||
equal(view.el, div); | ||
ok(view.$el instanceof Backbone.$); | ||
equal(view.$el[0], div); | ||
}); | ||
|
||
test("initialize", 1, function() { | ||
|
@@ -76,15 +70,15 @@ | |
equal(counter2, 3); | ||
}); | ||
|
||
test("delegate", 2, function() { | ||
test("_delegate", 2, function() { | ||
var counter1 = 0, counter2 = 0; | ||
|
||
var view = new Backbone.View({el: '#testElement'}); | ||
view.increment = function(){ counter1++; }; | ||
view.$el.on('click', function(){ counter2++; }); | ||
|
||
view.delegate('click', 'h1', view.increment); | ||
view.delegate('click', view.increment); | ||
view._delegate('click', 'h1', view.increment); | ||
view._delegate('click', view.increment); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesnt this test fail? I'm ok with the new method signature, but I rather like the no second argument form. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope test passed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check it again... the wrong logic branch gets called (it's why I had the |
||
|
||
view.$('h1').trigger('click'); | ||
equal(counter1, 2); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds an extra call to$.fn.off on the element, and creates a potentially very slow path on what is supposed to be hot code. Where previously $ .fn.off wouldn't have to do any mucking in namespaces since it's about to be removed, now it has to do both a slow and fast listener remove.
I'm in favor of letting the native view be responsible for removing events before deleting the element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right this is slower, but remember this isn't the hot path. The hot path is context creation and event delegation.
Sent from my iPhone
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but potentially if you're deleting many rows, items, whatever, while creating new ones constantly it will be a hot path. That's what this PR was supposed to address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Just trying the find the right balance of abstraction and performance. When that extra function becomes a problem, you can still override remove. But trust me, no matter how you shake it, that one call to parent.removeChild's got to be way faster then jquery to offset the cost. I can run some jsperf later tonight when I get home.
Sent from my iPhone