Reputation: 1566
I'm trying to make a table component in Knockout that can render arbitrary columns in a way that still uses Knockout for cell contents. I'm passing an array of column definition objects into the component and an array of arbitrary row objects. Then, I have a nested foreach structure that looks a bit like this:
<tbody data-bind="foreach: {data:rows, as:'row'}">
<tr data-bind="foreach: $parent.columns">
<td data-bind="html:renderCell(row)"></td>
</tr>
</tbody>
With this, I can allow the 'renderCell' function for each column to return some html to go in the cell, given the context of the row viewModel.
However, what I really want is to be able to return a Knockout template for the cell. I don't want to use <script type="text/html" id="xyz">
style templates because it doesn't suit this particular application, but I can't figure out how to get Knockout to treat the output from renderCell() as a template string.
How can I do something like the following and make it work?
<td data-bind="template:{fn: renderCell(row)}"></td>
I'd like to use other components like and other bindings in the output of the renderCell function.
Upvotes: 0
Views: 1048
Reputation: 14417
So as I understand it, you want a custom template for each cell. This template is to be based on the information coming into the binding. The closet thing I can think of to let you do this is a custom binding handler:
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever any observables/computeds that are accessed change
// Update the DOM element based on the supplied values here.
}
};
So that's the basic one from the knockout documentation. I'm guessing in the init function you can then select some html you wish to display:
function customTemplateOne(dataToBind) {
var spanEl = document.createElement('span');
spanEl.innerHTML = dataToBind;
return spanEl;
}
You can have a whole bunch of different functions to define different templates.
In your init you can do this:
var template = "";
switch (valueToDetermineTemplateChange)
{
case "useThis":
template = customTemplateOne(dataToBind);
}
Or you can take advantage of JavaScripts key value stuff.
var templates = {
useThis: function () {}
}
var template = templates[valueToDetermineTemplateChange]();
In order to do custom options you can do this:
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
var options = {};
ko.extend(options, ko.bindingHandlers.yourBindingName); // get the global options
ko.extend(options, ko.unwrap(valueAccessor())); // merge with the local options
// so if you have a data property on the options which holds the ko binding you can do this:
var data = ko.unwrap(options.data);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever any observables/computeds that are accessed change
// Update the DOM element based on the supplied values here.
},
options: {
customOption: "Some Option"
}
};
<div data-bind="yourBindingName: { data: someValue }"></div>
Instead of the applyBindingsToDescendants
function, you make your init a wrap of other bindings:
ko.applyBindingsToNode(element, { html: valueAccessor() }, context); // the html is a binding handler, you can specify with, text, foreach....
Upvotes: 1