Skip to content

Commit

Permalink
Fix multiple anonymous template bindings with same nodes, based on co…
Browse files Browse the repository at this point in the history
…de from @cervengoc
  • Loading branch information
mbest committed Apr 8, 2017
1 parent 6897d17 commit 10ec231
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 1 deletion.
30 changes: 30 additions & 0 deletions spec/templatingBehaviors.js
Expand Up @@ -518,6 +518,36 @@ describe('Templating', function() {
expect(testNode.childNodes[0]).toContainHtml("begin<span>the name is beta</span>end");
});

it('Should accept a "nodes" option that gives the template nodes, and able to use the same nodes for multiple bindings', function () {
testNode.innerHTML = "<div data-bind='template: { nodes: testNodes, data: testData1, bypassDomNodeWrap: true }'></div><div data-bind='template: { nodes: testNodes, data: testData2, bypassDomNodeWrap: true }'></div>";
var model = {
testNodes: [
document.createTextNode("begin"),
document.createElement("span"),
document.createTextNode("end")
],
testData1: ko.observable({ name: ko.observable("alpha1") }),
testData2: ko.observable({ name: ko.observable("alpha2") })
};
model.testNodes[1].setAttribute("data-bind", "text: name"); // See that bindings are applied to the injected nodes

ko.applyBindings(model, testNode);
expect(testNode.childNodes[0]).toContainText("beginalpha1end");
expect(testNode.childNodes[1]).toContainText("beginalpha2end");

// The injected bindings update to match model changes as usual
model.testData1().name("beta1");
model.testData2().name("beta2");
expect(testNode.childNodes[0]).toContainText("beginbeta1end");
expect(testNode.childNodes[1]).toContainText("beginbeta2end");

// The template binding re-renders successfully if model changes
model.testData1({ name: ko.observable("gamma1") });
model.testData2({ name: ko.observable("gamma2") });
expect(testNode.childNodes[0]).toContainText("begingamma1end");
expect(testNode.childNodes[1]).toContainText("begingamma2end");
});

it('Should accept a "nodes" option that gives the template nodes, and it can be used in conjunction with "foreach"', function() {
testNode.innerHTML = "<div data-bind='template: { nodes: testNodes, foreach: testData, bypassDomNodeWrap: true }'></div>";

Expand Down
11 changes: 10 additions & 1 deletion src/templating/templating.js
Expand Up @@ -217,6 +217,7 @@
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
}

var cleanContainerDomDataKey = ko.utils.domData.nextKey();
ko.bindingHandlers['template'] = {
'init': function(element, valueAccessor) {
// Support anonymous templates
Expand All @@ -233,7 +234,15 @@
if (ko.isObservable(nodes)) {
throw new Error('The "nodes" option must be a plain, non-observable array.');
}
var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent

// If the nodes are already attached to a KO-generated container, we reuse that container without moving the
// elements to a new one (we check only the first node, as the nodes are always moved together)
var container = nodes[0] && nodes[0].parentNode;
if (!container || !ko.utils.domData.get(container, cleanContainerDomDataKey)) {
container = ko.utils.moveCleanedNodesToContainerElement(nodes);
ko.utils.domData.set(container, cleanContainerDomDataKey, true);
}

new ko.templateSources.anonymousTemplate(element)['nodes'](container);
} else {
// It's an anonymous template - store the element contents, then clear the element
Expand Down

0 comments on commit 10ec231

Please sign in to comment.