Reputation: 19762
I am trying to solve a problem of rendering one template in context of another template with knockout. The outer template doesn't know and shouldn't care about the inner template and its view model. All it cares about is it's own template, a place to embed the inner template passing the name of it and its view model.
So ideally I wish I know how to implement the following binding:
<script type="text/html" id="outerTemplate">
<div class="outer-template" data-bind="here: {}"></div>
</script>
<!-- ko nested: { to: 'outerTemplate', data: { name: 'I am an inner view model' } } -->
<div class="inner-template" data-bind="text: name"></div>
<!-- /ko -->
If anyone knew the knockout well enough to easily outline such kind of binding I would greatly appreciate it.
UPDATE: The feature request was proposed: https://github.com/knockout/knockout/issues/1251
Upvotes: 0
Views: 904
Reputation: 19762
There is a working example I made myself: http://jsfiddle.net/m34wp/4/
var templateComputedDomDataKey = '__ko__templateComputedDomDataKey__';
function disposeOldComputedAndStoreNewOne(element, newComputed) {
var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
if(oldComputed && (typeof (oldComputed.dispose) == 'function')) {
oldComputed.dispose();
}
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
}
function makeArray(arrayLikeObject) {
var result = [];
for(var i = 0, j = arrayLikeObject.length; i < j; i++) {
result.push(arrayLikeObject[i]);
}
;
return result;
}
function moveCleanedNodesToContainerElement(nodes) {
var nodesArray = makeArray(nodes);
var container = document.createElement('div');
for(var i = 0, j = nodesArray.length; i < j; i++) {
container.appendChild(ko.cleanNode(nodesArray[i]));
}
return container;
}
ko.bindingHandlers['nested'] = {
'init': function (element, valueAccessor) {
var elementType = 1;
var commentType = 8;
var bindingValue = ko.utils.unwrapObservable(valueAccessor());
if(element.nodeType == elementType || element.nodeType == commentType) {
// It's an anonymous template - store the element contents, then clear the element
var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element);
var container = moveCleanedNodesToContainerElement(templateNodes);
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
}
return {
'controlsDescendantBindings': true
};
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.utils.unwrapObservable(valueAccessor());
var outerTemplateName = options['to'];
var dataValue = ko.utils.unwrapObservable(options['data']) || viewModel;
var innerContext = bindingContext['createChildContext'](dataValue);
innerContext.innerTemplateElement = element;
var templateComputed = ko.renderTemplate(outerTemplateName, innerContext, options, element);
disposeOldComputedAndStoreNewOne(element, templateComputed);
}
};
ko.bindingHandlers['here'] = {
'init': function (element, valueAccessor) {
return {
'controlsDescendantBindings': true
};
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var templateElement = bindingContext.innerTemplateElement;
if(viewModel != null) {
var innerContext = bindingContext['createChildContext'](viewModel);
var templateComputed = ko.renderTemplate(templateElement, innerContext, {
}, element);
disposeOldComputedAndStoreNewOne(element, templateComputed);
} else {
}
}
};
ko.virtualElements.allowedBindings['nested'] = true;
Upvotes: 0
Reputation: 114792
The template
binding allows you to dynamically select the template name to use, so you can do something like:
<script id="outer" type="text/html">
<h2>Outer</h2>
<div data-bind="template: { name: tmplName, data: data }"></div>
</script>
<script id="inner" type="text/html">
<h3>Inner</h3>
<input data-bind="value: name" />
</script>
<div data-bind="template: 'outer'"></div>
In this case the view model would look like:
var vm = {
tmplName: 'inner',
data: {
name: ko.observable("Bob")
}
};
ko.applyBindings(vm);
The view model could be structured however you want. The key is just that you are passing the template name and data into the template binding.
Sample: http://jsfiddle.net/rniemeyer/LHhc8/
Upvotes: 1