Make DOMRange stack traces more better

Switch all methods to `DOMRange.prototype.myMethod =` style.

Maybe we should just switch JSClass to this style.
This commit is contained in:
David Greenspan
2014-06-23 17:54:01 -07:00
parent a5b51c4f59
commit d2e8596bb2

View File

@@ -99,301 +99,314 @@ DOMRange.forElement = function (elem) {
return range;
};
_.extend(DOMRange.prototype, {
attach: function (parentElement, nextNode, _isMove) {
// This method is called to insert the DOMRange into the DOM for
// the first time, but it's also used internally when
// updating the DOM.
//
// If _isMove is true, move this attached range to a different
// location under the same parentElement.
if (_isMove) {
if (! (this.parentElement === parentElement &&
this.attached))
throw new Error("Can only move an attached DOMRange, and only under the same parent element");
}
var members = this.members;
if (members.length) {
this.emptyRangePlaceholder = null;
for (var i = 0; i < members.length; i++) {
DOMRange._insert(members[i], parentElement, nextNode, _isMove);
}
} else {
var placeholder = document.createTextNode("");
this.emptyRangePlaceholder = placeholder;
parentElement.insertBefore(placeholder, nextNode || null);
}
this.attached = true;
this.parentElement = parentElement;
if (! _isMove) {
for(var i = 0; i < this.augmenters.length; i++)
this.augmenters[i].attach(this, parentElement);
}
},
setMembers: function (newNodeAndRangeArray) {
var newMembers = newNodeAndRangeArray;
if (! (newMembers && (typeof newMembers.length) === 'number'))
throw new Error("Expected array");
var oldMembers = this.members;
for (var i = 0; i < oldMembers.length; i++)
this._memberOut(oldMembers[i]);
for (var i = 0; i < newMembers.length; i++)
this._memberIn(newMembers[i]);
if (! this.attached) {
this.members = newMembers;
} else {
// don't do anything if we're going from empty to empty
if (newMembers.length || oldMembers.length) {
// detach the old members and insert the new members
var nextNode = this.lastNode().nextSibling;
var parentElement = this.parentElement;
this.detach();
this.members = newMembers;
this.attach(parentElement, nextNode);
}
}
},
firstNode: function () {
if (! this.attached)
throw new Error("Must be attached");
if (! this.members.length)
return this.emptyRangePlaceholder;
var m = this.members[0];
return (m instanceof DOMRange) ? m.firstNode() : m;
},
lastNode: function () {
if (! this.attached)
throw new Error("Must be attached");
if (! this.members.length)
return this.emptyRangePlaceholder;
var m = this.members[this.members.length - 1];
return (m instanceof DOMRange) ? m.lastNode() : m;
},
detach: function () {
if (! this.attached)
throw new Error("Must be attached");
var oldParentElement = this.parentElement;
var members = this.members;
if (members.length) {
for (var i = 0; i < members.length; i++) {
DOMRange._remove(members[i]);
}
} else {
var placeholder = this.emptyRangePlaceholder;
this.parentElement.removeChild(placeholder);
this.emptyRangePlaceholder = null;
}
this.attached = false;
this.parentElement = null;
for(var i = 0; i < this.augmenters.length; i++)
this.augmenters[i].detach(this, oldParentElement);
},
addMember: function (newMember, atIndex, _isMove) {
var members = this.members;
if (! (atIndex >= 0 && atIndex <= members.length))
throw new Error("Bad index in range.addMember: " + atIndex);
if (! _isMove)
this._memberIn(newMember);
if (! this.attached) {
// currently detached; just updated members
members.splice(atIndex, 0, newMember);
} else if (members.length === 0) {
// empty; use the empty-to-nonempty handling of setMembers
this.setMembers([newMember]);
} else {
var nextNode;
if (atIndex === members.length) {
// insert at end
nextNode = this.lastNode().nextSibling;
} else {
var m = members[atIndex];
nextNode = (m instanceof DOMRange) ? m.firstNode() : m;
}
members.splice(atIndex, 0, newMember);
DOMRange._insert(newMember, this.parentElement, nextNode, _isMove);
}
},
removeMember: function (atIndex, _isMove) {
var members = this.members;
if (! (atIndex >= 0 && atIndex < members.length))
throw new Error("Bad index in range.removeMember: " + atIndex);
if (_isMove) {
members.splice(atIndex, 1);
} else {
var oldMember = members[atIndex];
this._memberOut(oldMember);
if (members.length === 1) {
// becoming empty; use the logic in setMembers
this.setMembers(_emptyArray);
} else {
members.splice(atIndex, 1);
if (this.attached)
DOMRange._remove(oldMember);
}
}
},
moveMember: function (oldIndex, newIndex) {
var member = this.members[oldIndex];
this.removeMember(oldIndex, true /*_isMove*/);
this.addMember(member, newIndex, true /*_isMove*/);
},
getMember: function (atIndex) {
var members = this.members;
if (! (atIndex >= 0 && atIndex < members.length))
throw new Error("Bad index in range.getMember: " + atIndex);
return this.members[atIndex];
},
stop: function () {
var stopCallbacks = this.stopCallbacks;
for (var i = 0; i < stopCallbacks.length; i++)
stopCallbacks[i].call(this);
this.stopCallbacks = _emptyArray;
},
onstop: function (cb) {
if (this.stopCallbacks === _emptyArray)
this.stopCallbacks = [];
this.stopCallbacks.push(cb);
},
_memberIn: function (m) {
if (m instanceof DOMRange)
m.parentRange = this;
else if (m.nodeType === 1) // DOM Element
m.$blaze_range = this;
},
_memberOut: function (m) {
// old members are almost always GCed immediately.
// to avoid the potentialy performance hit of deleting
// a property, we simple null it out.
if (m instanceof DOMRange)
m.parentRange = null;
else if (m.nodeType === 1) // DOM Element
m.$blaze_range = null;
},
containsElement: function (elem) {
if (! this.attached)
throw new Error("Must be attached");
// An element is contained in this DOMRange if it's possible to
// reach it by walking parent pointers, first through the DOM and
// then parentRange pointers. In other words, the element or some
// ancestor of it is at our level of the DOM (a child of our
// parentElement), and this element is one of our members or
// is a member of a descendant Range.
if (! Blaze._elementContains(this.parentElement, elem))
return false;
while (elem.parentNode !== this.parentElement)
elem = elem.parentElement;
var range = elem.$blaze_range;
while (range && range !== this)
range = range.parentRange;
return range === this;
},
containsRange: function (range) {
if (! this.attached)
throw new Error("Must be attached");
if (! range.attached)
return false;
// A DOMRange is contained in this DOMRange if it's possible
// to reach this range by following parent pointers. If the
// DOMRange has the same parentElement, then it should be
// a member, or a member of a member etc. Otherwise, we must
// contain its parentElement.
if (range.parentElement !== this.parentElement)
return this.containsElement(range.parentElement);
if (range === this)
return false; // don't contain self
while (range && range !== this)
range = range.parentRange;
return range === this;
},
addDOMAugmenter: function (augmenter) {
if (this.augmenters === _emptyArray)
this.augmenters = [];
this.augmenters.push(augmenter);
},
$: function (selector) {
var self = this;
var parentNode = this.parentElement;
if (! parentNode)
throw new Error("Can't select in removed DomRange");
// Strategy: Find all selector matches under parentNode,
// then filter out the ones that aren't in this DomRange
// using `DOMRange#containsElement`. This is
// asymptotically slow in the presence of O(N) sibling
// content that is under parentNode but not in our range,
// so if performance is an issue, the selector should be
// run on a child element.
// Since jQuery can't run selectors on a DocumentFragment,
// we don't expect findBySelector to work.
if (parentNode.nodeType === 11 /* DocumentFragment */)
throw new Error("Can't use $ on an offscreen range");
var results = Blaze.DOMBackend.findBySelector(selector, parentNode);
// We don't assume `results` has jQuery API; a plain array
// should do just as well. However, if we do have a jQuery
// array, we want to end up with one also, so we use
// `.filter`.
// Function that selects only elements that are actually
// in this DomRange, rather than simply descending from
// `parentNode`.
var filterFunc = function (elem) {
// handle jQuery's arguments to filter, where the node
// is in `this` and the index is the first argument.
if (typeof elem === 'number')
elem = this;
return self.containsElement(elem);
};
if (! results.filter) {
// not a jQuery array, and not a browser with
// Array.prototype.filter (e.g. IE <9)
var newResults = [];
for (var i = 0; i < results.length; i++) {
var x = results[i];
if (filterFunc(x))
newResults.push(x);
}
results = newResults;
} else {
// `results.filter` is either jQuery's or ECMAScript's `filter`
results = results.filter(filterFunc);
}
return results;
DOMRange.prototype.attach = function (parentElement, nextNode, _isMove) {
// This method is called to insert the DOMRange into the DOM for
// the first time, but it's also used internally when
// updating the DOM.
//
// If _isMove is true, move this attached range to a different
// location under the same parentElement.
if (_isMove) {
if (! (this.parentElement === parentElement &&
this.attached))
throw new Error("Can only move an attached DOMRange, and only under the same parent element");
}
});
var members = this.members;
if (members.length) {
this.emptyRangePlaceholder = null;
for (var i = 0; i < members.length; i++) {
DOMRange._insert(members[i], parentElement, nextNode, _isMove);
}
} else {
var placeholder = document.createTextNode("");
this.emptyRangePlaceholder = placeholder;
parentElement.insertBefore(placeholder, nextNode || null);
}
this.attached = true;
this.parentElement = parentElement;
if (! _isMove) {
for(var i = 0; i < this.augmenters.length; i++)
this.augmenters[i].attach(this, parentElement);
}
};
DOMRange.prototype.setMembers = function (newNodeAndRangeArray) {
var newMembers = newNodeAndRangeArray;
if (! (newMembers && (typeof newMembers.length) === 'number'))
throw new Error("Expected array");
var oldMembers = this.members;
for (var i = 0; i < oldMembers.length; i++)
this._memberOut(oldMembers[i]);
for (var i = 0; i < newMembers.length; i++)
this._memberIn(newMembers[i]);
if (! this.attached) {
this.members = newMembers;
} else {
// don't do anything if we're going from empty to empty
if (newMembers.length || oldMembers.length) {
// detach the old members and insert the new members
var nextNode = this.lastNode().nextSibling;
var parentElement = this.parentElement;
this.detach();
this.members = newMembers;
this.attach(parentElement, nextNode);
}
}
};
DOMRange.prototype.firstNode = function () {
if (! this.attached)
throw new Error("Must be attached");
if (! this.members.length)
return this.emptyRangePlaceholder;
var m = this.members[0];
return (m instanceof DOMRange) ? m.firstNode() : m;
};
DOMRange.prototype.lastNode = function () {
if (! this.attached)
throw new Error("Must be attached");
if (! this.members.length)
return this.emptyRangePlaceholder;
var m = this.members[this.members.length - 1];
return (m instanceof DOMRange) ? m.lastNode() : m;
};
DOMRange.prototype.detach = function () {
if (! this.attached)
throw new Error("Must be attached");
var oldParentElement = this.parentElement;
var members = this.members;
if (members.length) {
for (var i = 0; i < members.length; i++) {
DOMRange._remove(members[i]);
}
} else {
var placeholder = this.emptyRangePlaceholder;
this.parentElement.removeChild(placeholder);
this.emptyRangePlaceholder = null;
}
this.attached = false;
this.parentElement = null;
for(var i = 0; i < this.augmenters.length; i++)
this.augmenters[i].detach(this, oldParentElement);
};
DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) {
var members = this.members;
if (! (atIndex >= 0 && atIndex <= members.length))
throw new Error("Bad index in range.addMember: " + atIndex);
if (! _isMove)
this._memberIn(newMember);
if (! this.attached) {
// currently detached; just updated members
members.splice(atIndex, 0, newMember);
} else if (members.length === 0) {
// empty; use the empty-to-nonempty handling of setMembers
this.setMembers([newMember]);
} else {
var nextNode;
if (atIndex === members.length) {
// insert at end
nextNode = this.lastNode().nextSibling;
} else {
var m = members[atIndex];
nextNode = (m instanceof DOMRange) ? m.firstNode() : m;
}
members.splice(atIndex, 0, newMember);
DOMRange._insert(newMember, this.parentElement, nextNode, _isMove);
}
};
DOMRange.prototype.removeMember = function (atIndex, _isMove) {
var members = this.members;
if (! (atIndex >= 0 && atIndex < members.length))
throw new Error("Bad index in range.removeMember: " + atIndex);
if (_isMove) {
members.splice(atIndex, 1);
} else {
var oldMember = members[atIndex];
this._memberOut(oldMember);
if (members.length === 1) {
// becoming empty; use the logic in setMembers
this.setMembers(_emptyArray);
} else {
members.splice(atIndex, 1);
if (this.attached)
DOMRange._remove(oldMember);
}
}
};
DOMRange.prototype.moveMember = function (oldIndex, newIndex) {
var member = this.members[oldIndex];
this.removeMember(oldIndex, true /*_isMove*/);
this.addMember(member, newIndex, true /*_isMove*/);
};
DOMRange.prototype.getMember = function (atIndex) {
var members = this.members;
if (! (atIndex >= 0 && atIndex < members.length))
throw new Error("Bad index in range.getMember: " + atIndex);
return this.members[atIndex];
};
DOMRange.prototype.stop = function () {
var stopCallbacks = this.stopCallbacks;
for (var i = 0; i < stopCallbacks.length; i++)
stopCallbacks[i].call(this);
this.stopCallbacks = _emptyArray;
};
DOMRange.prototype.onstop = function (cb) {
if (this.stopCallbacks === _emptyArray)
this.stopCallbacks = [];
this.stopCallbacks.push(cb);
};
DOMRange.prototype._memberIn = function (m) {
if (m instanceof DOMRange)
m.parentRange = this;
else if (m.nodeType === 1) // DOM Element
m.$blaze_range = this;
};
DOMRange.prototype._memberOut = function (m) {
// old members are almost always GCed immediately.
// to avoid the potentialy performance hit of deleting
// a property, we simple null it out.
if (m instanceof DOMRange)
m.parentRange = null;
else if (m.nodeType === 1) // DOM Element
m.$blaze_range = null;
};
DOMRange.prototype.containsElement = function (elem) {
if (! this.attached)
throw new Error("Must be attached");
// An element is contained in this DOMRange if it's possible to
// reach it by walking parent pointers, first through the DOM and
// then parentRange pointers. In other words, the element or some
// ancestor of it is at our level of the DOM (a child of our
// parentElement), and this element is one of our members or
// is a member of a descendant Range.
if (! Blaze._elementContains(this.parentElement, elem))
return false;
while (elem.parentNode !== this.parentElement)
elem = elem.parentElement;
var range = elem.$blaze_range;
while (range && range !== this)
range = range.parentRange;
return range === this;
};
DOMRange.prototype.containsRange = function (range) {
if (! this.attached)
throw new Error("Must be attached");
if (! range.attached)
return false;
// A DOMRange is contained in this DOMRange if it's possible
// to reach this range by following parent pointers. If the
// DOMRange has the same parentElement, then it should be
// a member, or a member of a member etc. Otherwise, we must
// contain its parentElement.
if (range.parentElement !== this.parentElement)
return this.containsElement(range.parentElement);
if (range === this)
return false; // don't contain self
while (range && range !== this)
range = range.parentRange;
return range === this;
};
DOMRange.prototype.addDOMAugmenter = function (augmenter) {
if (this.augmenters === _emptyArray)
this.augmenters = [];
this.augmenters.push(augmenter);
};
DOMRange.prototype.$ = function (selector) {
var self = this;
var parentNode = this.parentElement;
if (! parentNode)
throw new Error("Can't select in removed DomRange");
// Strategy: Find all selector matches under parentNode,
// then filter out the ones that aren't in this DomRange
// using `DOMRange#containsElement`. This is
// asymptotically slow in the presence of O(N) sibling
// content that is under parentNode but not in our range,
// so if performance is an issue, the selector should be
// run on a child element.
// Since jQuery can't run selectors on a DocumentFragment,
// we don't expect findBySelector to work.
if (parentNode.nodeType === 11 /* DocumentFragment */)
throw new Error("Can't use $ on an offscreen range");
var results = Blaze.DOMBackend.findBySelector(selector, parentNode);
// We don't assume `results` has jQuery API; a plain array
// should do just as well. However, if we do have a jQuery
// array, we want to end up with one also, so we use
// `.filter`.
// Function that selects only elements that are actually
// in this DomRange, rather than simply descending from
// `parentNode`.
var filterFunc = function (elem) {
// handle jQuery's arguments to filter, where the node
// is in `this` and the index is the first argument.
if (typeof elem === 'number')
elem = this;
return self.containsElement(elem);
};
if (! results.filter) {
// not a jQuery array, and not a browser with
// Array.prototype.filter (e.g. IE <9)
var newResults = [];
for (var i = 0; i < results.length; i++) {
var x = results[i];
if (filterFunc(x))
newResults.push(x);
}
results = newResults;
} else {
// `results.filter` is either jQuery's or ECMAScript's `filter`
results = results.filter(filterFunc);
}
return results;
};
Blaze.DOMAugmenter = JSClass.create({
attach: function (range, element) {},