DomRanges have owners; find ranges under element

This commit is contained in:
David Greenspan
2013-08-26 23:15:01 -07:00
parent 5e2a5fdc53
commit 3867c69e56
2 changed files with 81 additions and 18 deletions

View File

@@ -56,8 +56,6 @@ var isSignificantNode = function (n) {
/^\s+$/.test(n.nodeValue)));
};
var nextColor = 1;
var DomRange = function (component) {
var start = document.createTextNode("");
var end = document.createTextNode("");
@@ -79,6 +77,7 @@ var DomRange = function (component) {
this.members = {};
this.nextMemberId = 1;
this.owner = null;
};
_extend(DomRange.prototype, {
@@ -154,6 +153,7 @@ _extend(DomRange.prototype, {
throw new Error("Component not built");
// Range
var range = newMember.dom;
range.owner = this.component;
var nodes = range.getNodes();
for (var i = 0; i < nodes.length; i++)
insertNode(nodes[i], parentNode, nextNode);
@@ -172,6 +172,7 @@ _extend(DomRange.prototype, {
this.removeAll();
removeNode(this.start);
removeNode(this.end);
this.owner = null;
return;
}
@@ -194,6 +195,7 @@ _extend(DomRange.prototype, {
if ('dom' in member) {
// Range
var range = member.dom;
range.owner = null;
// Don't mind if range (specifically its start
// marker) has been removed already.
if (range.start.parentNode === parentNode)
@@ -265,10 +267,12 @@ _extend(DomRange.prototype, {
if ('dom' in mem) {
// Range
var range = mem.dom;
if (range.start.parentNode === parentNode)
if (range.start.parentNode === parentNode) {
rangeFunc && rangeFunc(range); // still there
else
} else {
range.owner = null;
delete members[k]; // gone
}
} else {
// Node
var node = mem;
@@ -316,8 +320,6 @@ _extend(DomRange.prototype, {
// see `getInsertionPoint`. Adding multiple members
// at once using `add(array)` is faster.
refresh: function () {
var color = nextColor++;
this.color = color;
var someNode = null;
var someRange = null;
@@ -329,7 +331,6 @@ _extend(DomRange.prototype, {
numMembers++;
}, function (range) {
range.refresh();
range.color = color;
someRange = range;
numMembers++;
});
@@ -366,14 +367,17 @@ _extend(DomRange.prototype, {
for (var node = parentNode.firstChild;
node; node = node.nextSibling) {
var nodeOwner;
if (node.$ui &&
((node.$ui.dom === this &&
(nodeOwner = node.$ui.dom) &&
((nodeOwner === this &&
node !== this.start &&
node !== this.end &&
isSignificantNode(node)) ||
(node.$ui.dom !== this &&
node.$ui.dom.color === color &&
node.$ui.dom.start === node))) {
(nodeOwner !== this &&
nodeOwner.owner &&
nodeOwner.owner.dom === this &&
nodeOwner.start === node))) {
// found a member range or node
// (excluding "insignificant" empty text nodes,
// which won't be moved by, say, jQuery)
@@ -409,11 +413,11 @@ _extend(DomRange.prototype, {
// nodes as well.
for (var n;
(n = firstNode.previousSibling) &&
n.$ui && n.$ui.dom.color === color;)
n.$ui && n.$ui.dom === this;)
firstNode = n;
for (var n;
(n = lastNode.nextSibling) &&
n.$ui && n.$ui.dom.color === color;)
n.$ui && n.$ui.dom === this;)
lastNode = n;
// adjust our start/end pointers
if (firstNode !== this.start)
@@ -452,6 +456,8 @@ _extend(DomRange.prototype, {
// still there
range.refresh();
return range.start;
} else {
range.owner = null;
}
} else {
// Node
@@ -467,4 +473,32 @@ _extend(DomRange.prototype, {
}
});
// In a real-life case where you need a refresh,
// you probably don't have easy
// access to the appropriate DomRange or component,
// just the enclosing element:
//
// ```
// {{#Sortable}}
// <div>
// {{#each}}
// ...
// ```
//
// In this case, Sortable wants to call `refresh`
// on the div, not the each, so it would use this function.
DomRange.refresh = function (element) {
var topLevelRanges = [];
for (var n = element.firstChild;
n; n = n.nextSibling) {
if (n.$ui && n === n.$ui.dom.start &&
! n.$ui.dom.owner)
topLevelRanges.push(n.$ui.dom);
}
for (var i = 0, N = topLevelRanges.length;
i < N; i++)
topLevelRanges[i].refresh();
};
UI.DomRange = DomRange;

View File

@@ -52,6 +52,8 @@ Tinytest.add("ui - DomRange - basic", function (test) {
s.add(span);
r.add(s);
test.equal(_.keys(r.members).length, 2);
test.isFalse(r.owner);
test.equal(s.owner, r);
// DOM should go: rStart, DIV, sStart, SPAN, sEnd, rEnd.
test.equal(span.previousSibling, s.startNode());
@@ -73,6 +75,7 @@ Tinytest.add("ui - DomRange - basic", function (test) {
// removal
s.remove();
test.isFalse(s.owner);
// sStart, SPAN, sEnd are gone from the DOM.
test.equal(rStart.nextSibling, div);
test.equal(rEnd.previousSibling, div);
@@ -168,6 +171,11 @@ Tinytest.add("ui - DomRange - shuffling", function (test) {
test.equal(r.get('I'), I);
test.equal(r.get('U'), U);
test.isFalse(r.owner);
test.equal(X.dom.owner, r);
test.equal(Y.dom.owner, r);
test.equal(Z.dom.owner, r);
r.remove('Y');
test.equal(spellDom(), '((Z)(X)BAAIU)');
test.equal(r.get('Y'), null);
@@ -203,7 +211,7 @@ Tinytest.add("ui - DomRange - nested", function (test) {
// nest empty ranges; should work even though
// there are no element nodes
var A,B,C,D,E,F,G;
var A,B,C,D,E,F;
test.equal(spellDom(), '()');
r.add(A = new Comp('A'));
@@ -228,9 +236,13 @@ Tinytest.add("ui - DomRange - nested", function (test) {
test.equal(spellDom(), '(AaCcBFfDdb)');
r.remove('B');
test.equal(spellDom(), '(AaCc)');
test.isFalse(r.owner);
test.equal(A.dom.owner, r);
test.equal(C.dom.owner, r);
});
Tinytest.add("ui - DomRange - outside moves", function (test) {
Tinytest.add("ui - DomRange - external moves", function (test) {
// In this one, uppercase letters are div elements,
// lowercase letters are marker text nodes, as follows:
//
@@ -323,6 +335,13 @@ Tinytest.add("ui - DomRange - outside moves", function (test) {
test.equal(spellDom(),
strip('(a-X-b - c-d-Y-Z-e-f - g-h-i-W-j-k-l V)'));
test.equal(ab.dom.owner, r);
test.equal(cf.dom.owner, r);
test.equal(de.dom.owner, cf);
test.equal(gl.dom.owner, r);
test.equal(hk.dom.owner, gl);
test.equal(ij.dom.owner, hk);
// all right, now let's mess around with these elements!
$([Y,Z]).insertBefore(X);
@@ -436,9 +455,19 @@ Tinytest.add("ui - DomRange - outside moves", function (test) {
//
// See `range.getInsertionPoint`.
r.refresh();
// Same as `r.refresh()` but tests
// the convenience function `DomRange.refresh(element)`:
DomRange.refresh(r.parentNode());
r.moveBefore('gl', null);
test.equal(spellDom(),
strip('------------- (- aXb ghiWjkl)'));
});
strip('-------------- (aXb ghiWjkl)'));
});
// TO TEST STILL:
// - external remove element
// - double-add, double-remove
// - external entire remove
// - element adoption during move/remove/refresh