Utility method for efficiently managing child nodes

This commit is contained in:
Tim Schaub
2018-11-10 08:03:56 -07:00
parent a69eeceeba
commit fc6882f146
2 changed files with 204 additions and 1 deletions

View File

@@ -79,3 +79,46 @@ export function removeChildren(node) {
node.removeChild(node.lastChild);
}
}
/**
* Transform the children of a parent node so they match the
* provided list of children. This function aims to efficiently
* remove, add, and reorder child nodes while maintaining a simple
* implementation (it is not guaranteed to minimize DOM operations).
* @param {Node} node The parent node whose children need reworking.
* @param {Array<Node>} children The desired children.
*/
export function replaceChildren(node, children) {
const oldChildren = node.childNodes;
for (let i = 0; true; ++i) {
const oldChild = oldChildren[i];
const newChild = children[i];
// check if our work is done
if (!oldChild && !newChild) {
break;
}
// check if children match
if (oldChild === newChild) {
continue;
}
// check if a new child needs to be added
if (!oldChild) {
node.appendChild(newChild);
continue;
}
// check if an old child needs to be removed
if (!newChild) {
node.removeChild(oldChild);
--i;
continue;
}
// reorder
node.insertBefore(newChild, oldChild);
}
}

View File

@@ -1,4 +1,4 @@
import {createCanvasContext2D, outerWidth, outerHeight} from '../../../../src/ol/dom.js';
import {createCanvasContext2D, outerWidth, outerHeight, replaceChildren} from '../../../../src/ol/dom.js';
describe('ol.dom', function() {
@@ -344,4 +344,164 @@ describe('ol.dom', function() {
});
describe('replaceChildren()', function() {
function assertChildrenMatch(parent, children) {
const actual = parent.childNodes;
expect(actual).to.have.length(children.length);
for (let i = 0; i < children.length; i++) {
expect(actual[i]).to.be(children[i]);
}
}
it('adds new children to an empty parent', function() {
const parent = document.createElement('div');
const children = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c')
];
replaceChildren(parent, children);
assertChildrenMatch(parent, children);
});
it('removes children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
replaceChildren(parent, []);
expect(parent.childNodes).to.have.length(0);
});
it('swaps children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
const newChildren = [
document.createElement('d'),
document.createElement('e'),
document.createElement('f')
];
replaceChildren(parent, newChildren);
assertChildrenMatch(parent, newChildren);
});
it('appends children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
const newChildren = [
document.createElement('d'),
document.createElement('e'),
document.createElement('f')
];
const allChildren = existingChildren.concat(newChildren);
replaceChildren(parent, allChildren);
assertChildrenMatch(parent, allChildren);
});
it('prunes children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c'),
document.createElement('d'),
document.createElement('e')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
const desiredChildren = [
existingChildren[1],
existingChildren[3]
];
replaceChildren(parent, desiredChildren);
assertChildrenMatch(parent, desiredChildren);
});
it('reorders children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c'),
document.createElement('d'),
document.createElement('e')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
const desiredChildren = [
existingChildren[1],
existingChildren[3],
existingChildren[0],
existingChildren[4],
existingChildren[2]
];
replaceChildren(parent, desiredChildren);
assertChildrenMatch(parent, desiredChildren);
});
it('reorders, prunes, and appends children', function() {
const parent = document.createElement('div');
const existingChildren = [
document.createElement('a'),
document.createElement('b'),
document.createElement('c'),
document.createElement('d'),
document.createElement('e')
];
existingChildren.forEach(function(child) {
parent.appendChild(child);
});
const desiredChildren = [
document.createElement('f'),
existingChildren[3],
document.createElement('g'),
existingChildren[0],
existingChildren[2]
];
const clone = desiredChildren.slice();
replaceChildren(parent, desiredChildren);
assertChildrenMatch(parent, desiredChildren);
// confirm we haven't modified the input
expect(desiredChildren).to.eql(clone);
});
});
});