diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index f2d4cef524..dc851b1f8d 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -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}} +//
+// {{#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; \ No newline at end of file diff --git a/packages/ui/domrange_tests.js b/packages/ui/domrange_tests.js index dfcadba032..97234d450e 100644 --- a/packages/ui/domrange_tests.js +++ b/packages/ui/domrange_tests.js @@ -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)')); -}); \ No newline at end of file + strip('-------------- (aXb ghiWjkl)')); +}); + + +// TO TEST STILL: +// - external remove element +// - double-add, double-remove +// - external entire remove +// - element adoption during move/remove/refresh \ No newline at end of file