dom-layer
Version:
Virtual DOM implementation.
113 lines (106 loc) • 4.22 kB
JavaScript
var isEmpty = require('is-empty');
var isArray = Array.isArray;
/**
* Patches & Reorders child nodes of a container (i.e represented by `fromChildren`) to match `toChildren`.
*
* Since finding the longest common subsequence problem is NP-hard, this implementation
* is a simple heuristic for reordering nodes with a 'minimum' of moves in O(n).
*
* @param Object container The parent container.
* @param Array children The current array of children.
* @param Array toChildren The new array of children to reach.
* @param Object parent The parent virtual node.
*/
function patch(container, children, toChildren, parent) {
var fromChildren = children.slice();
var fromStartIndex = 0, toStartIndex = 0;
var fromEndIndex = fromChildren.length - 1;
var fromStartNode = fromChildren[0];
var fromEndNode = fromChildren[fromEndIndex];
var toEndIndex = toChildren.length - 1;
var toStartNode = toChildren[0];
var toEndNode = toChildren[toEndIndex];
var indexes, index, node, before;
while (fromStartIndex <= fromEndIndex && toStartIndex <= toEndIndex) {
if (fromStartNode == undefined) {
fromStartNode = fromChildren[++fromStartIndex];
} else if (fromEndNode == undefined) {
fromEndNode = fromChildren[--fromEndIndex];
} else if (toStartNode == undefined) {
toStartNode = toChildren[++toStartIndex];
} else if (toEndNode == undefined) {
toEndNode = toChildren[--toEndIndex];
} else if (fromStartNode.match(toStartNode)) {
fromStartNode.patch(toStartNode);
fromStartNode = fromChildren[++fromStartIndex];
toStartNode = toChildren[++toStartIndex];
} else if (fromEndNode.match(toEndNode)) {
fromEndNode.patch(toEndNode);
fromEndNode = fromChildren[--fromEndIndex];
toEndNode = toChildren[--toEndIndex];
} else if (fromStartNode.match(toEndNode)) {
fromStartNode.patch(toEndNode);
container.insertBefore(fromStartNode.element, fromEndNode.element.nextSibling);
fromStartNode = fromChildren[++fromStartIndex];
toEndNode = toChildren[--toEndIndex];
} else if (fromEndNode.match(toStartNode)) {
fromEndNode.patch(toStartNode);
container.insertBefore(fromEndNode.element, fromStartNode.element);
fromEndNode = fromChildren[--fromEndIndex];
toStartNode = toChildren[++toStartIndex];
} else {
if (indexes === undefined) {
indexes = keysIndexes(fromChildren, fromStartIndex, fromEndIndex);
}
index = indexes[toStartNode.key];
if (index === undefined) {
container.insertBefore(toStartNode.render(container, parent), fromStartNode.element);
toStartNode = toChildren[++toStartIndex];
} else {
node = fromChildren[index];
if (!node.match(toStartNode)) {
container.insertBefore(toStartNode.render(node.element.parentNode, toStartNode.parent), fromStartNode.element);
} else {
node.patch(toStartNode);
fromChildren[index] = undefined;
container.insertBefore(node.element, fromStartNode.element);
}
toStartNode = toChildren[++toStartIndex];
}
}
}
if (fromStartIndex > fromEndIndex) {
before = toChildren[toEndIndex + 1] == undefined ? null : toChildren[toEndIndex + 1].element;
for (; toStartIndex <= toEndIndex; toStartIndex++) {
if (toChildren[toStartIndex] != undefined) {
container.insertBefore(toChildren[toStartIndex].render(container, parent), before);
}
}
} else if (toStartIndex > toEndIndex) {
for (; fromStartIndex <= fromEndIndex; fromStartIndex++) {
if (fromChildren[fromStartIndex] != undefined) {
fromChildren[fromStartIndex].remove();
}
}
}
return toChildren;
}
/**
* Returns indexes of keyed nodes.
*
* @param Array children An array of nodes.
* @return Object An object of keyed nodes indexes.
*/
function keysIndexes(children, startIndex, endIndex) {
var i, keys = Object.create(null), key;
for (i = startIndex; i <= endIndex; ++i) {
if (children[i]) {
key = children[i].key;
if (key !== undefined) {
keys[key] = i;
}
}
}
return keys;
}
module.exports = patch;