Skip to content

Commit

Permalink
Tolerate preProcessNode callbacks that remove nodes entirely (supplyi…
Browse files Browse the repository at this point in the history
…ng an empty array of replacements)
  • Loading branch information
SteveSanderson committed Jul 2, 2013
1 parent e30410c commit d82e6db
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 6 deletions.
13 changes: 12 additions & 1 deletion spec/bindingAttributeBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,22 @@ describe('Binding attribute syntax', function() {
return newNodes;
}

// Example: Remove any <button> elements
if (node.tagName && node.tagName.toLowerCase() === "button") {
node.parentNode.removeChild(node);
return []; // Replace with empty array
}

// Example: Leave the node unchanged.
return null;
};

testNode.innerHTML = "<prop1></prop1>, <p data-bind='text: prop2'></p>, prefix {{ prop3 }} suffix<div data-bind='foreach: prop4'> {{ prop3 }} x</div>";
testNode.innerHTML = "<prop1></prop1>, "
+ "<p data-bind='text: prop2'></p>, "
+ "prefix {{ prop3 }} suffix"
+ "<div data-bind='foreach: prop4'>"
+ "<button>DeleteMe</button> {{ prop3 }} x<button>DeleteMe</button>"
+ "</div>";
var vm = {
prop1: 'PROP1VAL',
prop2: 'PROP2VAL',
Expand Down
23 changes: 18 additions & 5 deletions src/templating/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
nextInQueue = ko.virtualElements.nextSibling(node);
action(node);
action(node, nextInQueue);
}
}

Expand All @@ -28,15 +28,28 @@
preProcessNode = provider['preProcessNode'];

if (preProcessNode) {
invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
// Because preProcessNodes can remove nodes, it's tricky to work out what is the last node in the
// continuous array after preprocessing. We need the last node from the last nonempty set of replacement
// nodes. To find it, lastNodeSeenDuringPreProcessing tracks the last such candidate we've seen.
var lastNodeSeenDuringPreProcessing = null;

invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node, nextNodeInRange) {
var newNodes = preProcessNode.call(provider, node);
if (newNodes) {
if (node === firstNode)
firstNode = newNodes[0];
if (node === lastNode)
lastNode = newNodes[newNodes.length-1];
firstNode = newNodes[0] || nextNodeInRange;
lastNodeSeenDuringPreProcessing = newNodes[newNodes.length-1] || lastNodeSeenDuringPreProcessing;
} else {
lastNodeSeenDuringPreProcessing = node;
}
});
lastNode = lastNodeSeenDuringPreProcessing;

// preProcessNode might have removed all the nodes, in which case there's nothing left to activate bindings on
if (!firstNode) { // Note that lastNode can only be null if firstNode is, so no need to check for that
return;
}

// Because preProcessNode can change the nodes, including the first and last nodes, replace
// continuousNodeArray with the latest first/last nodes (we don't actually need the inner nodes)
continuousNodeArray.length = 0;
Expand Down

0 comments on commit d82e6db

Please sign in to comment.