From 66c4a47c0dedd22c2204f90682be3209ef366377 Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Thu, 2 Jan 2014 15:54:11 -0500 Subject: [PATCH] Remove nodes from their previous structures When manipulation methods operate on existing nodes, ensure that these nodes are removed from their previous structures. --- lib/api/manipulation.js | 41 ++++++++++++++++++++++++++----- test/api.manipulation.js | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/lib/api/manipulation.js b/lib/api/manipulation.js index b142fb3adb..d6a81a02f2 100644 --- a/lib/api/manipulation.js +++ b/lib/api/manipulation.js @@ -31,22 +31,51 @@ var _insert = function(concatenator) { if (_.isFunction(elems[0])) { return this.each(function(i, el) { dom = makeDomArray(elems[0].call(el, i, this.html())); - updateDOM(concatenator(dom, el.children || (el.children = [])), el); + concatenator(dom, el.children); + updateDOM(el.children, el); }); } else { return domEach(this, function(i, el) { - updateDOM(concatenator(dom, el.children || (el.children = [])), el); + concatenator(dom, el.children); + updateDOM(el.children, el); }); } }; }; +/* + * Modify an array in-place, removing some number of elements and adding new + * elements directly following them. + * + * @param {Array} array Target array to splice. + * @param {Number} spliceIdx Index at which to begin changing the array. + * @param {Number} spliceCount Number of elements to remove from the array. + * @param {Array} newElems Elements to insert into the array. + * + * @api private + */ +var uniqueSplice = function(array, spliceIdx, spliceCount, newElems) { + var spliceArgs = [spliceIdx, spliceCount].concat(newElems); + var idx, len, prevIdx; + + // Before splicing in new elements, ensure they do not already appear in the + // current array. + for (idx = 0, len = newElems.length; idx < len; ++idx) { + prevIdx = array.indexOf(newElems[idx]); + if (prevIdx > -1) { + array.splice(prevIdx, 1); + } + } + + return array.splice.apply(array, spliceArgs); +}; + var append = exports.append = _insert(function(dom, children) { - return children.concat(dom); + uniqueSplice(children, children.length, 0, dom); }); var prepend = exports.prepend = _insert(function(dom, children) { - return dom.concat(children); + uniqueSplice(children, 0, 0, dom); }); var after = exports.after = function() { @@ -66,7 +95,7 @@ var after = exports.after = function() { } // Add element after `this` element - siblings.splice.apply(siblings, [++index, 0].concat(dom)); + uniqueSplice(siblings, ++index, 0, dom); // Update next, prev, and parent pointers updateDOM(siblings, parent); @@ -92,7 +121,7 @@ var before = exports.before = function() { } // Add element before `el` element - siblings.splice.apply(siblings, [index, 0].concat(dom)); + uniqueSplice(siblings, index, 0, dom); // Update next, prev, and parent pointers updateDOM(siblings, parent); diff --git a/test/api.manipulation.js b/test/api.manipulation.js index 2b618387b5..3f0b9511a4 100644 --- a/test/api.manipulation.js +++ b/test/api.manipulation.js @@ -30,6 +30,20 @@ describe('$(...)', function() { expect($fruits.children(3).hasClass('plum')).to.be.ok(); }); + it('(existing Node) : should remove node from previous location', function() { + var $fruits = $(fruits); + var apple = $fruits.children()[0]; + var $children; + + expect($fruits.children()).to.have.length(3); + $fruits.append(apple); + $children = $fruits.children(); + + expect($children).to.have.length(3); + expect($children[0]).to.not.equal(apple); + expect($children[2]).to.equal(apple); + }); + it('($(...), html) : should add multiple elements as last children', function() { var $fruits = $(fruits); var $plum = $('
  • Plum
  • '); @@ -158,6 +172,20 @@ describe('$(...)', function() { expect($fruits.children(0).hasClass('plum')).to.be.ok(); }); + it('(existing Node) : should remove existing nodes from previous locations', function() { + var $fruits = $(fruits); + var pear = $fruits.children()[2]; + var $children; + + expect($fruits.children()).to.have.length(3); + $fruits.prepend(pear); + $children = $fruits.children(); + + expect($children).to.have.length(3); + expect($children[2]).to.not.equal(pear); + expect($children[0]).to.equal(pear); + }); + it('(Array) : should add all elements in the array as inital children', function() { var $fruits = $(fruits); var more = $('
  • Plum
  • Grape
  • ') @@ -287,6 +315,18 @@ describe('$(...)', function() { expect($('.apple', $fruits).next().hasClass('plum')).to.be.ok(); }); + it('(existing Node) : should remove existing nodes from previous locations', function() { + var $fruits = $(fruits); + var pear = $fruits.children()[2]; + var $children; + + $('.apple', $fruits).after(pear); + + $children = $fruits.children(); + expect($children).to.have.length(3); + expect($children[1]).to.be(pear); + }); + it('($(...), html) : should add multiple elements as next siblings', function() { var $fruits = $(fruits); var $plum = $('
  • Plum
  • '); @@ -382,6 +422,18 @@ describe('$(...)', function() { expect($('.apple', $fruits).prev().hasClass('plum')).to.be.ok(); }); + it('(existing Node) : should remove existing nodes from previous locations', function() { + var $fruits = $(fruits); + var pear = $fruits.children()[2]; + var $children; + + $('.apple', $fruits).before(pear); + + $children = $fruits.children(); + expect($children).to.have.length(3); + expect($children[0]).to.be(pear); + }); + it('(Array) : should add all elements in the array as previous sibling', function() { var $fruits = $(fruits); var more = $('
  • Plum
  • Grape
  • ')