Utility method for efficiently managing child nodes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user