Mick Sear
Mick Sear

Reputation: 1566

Dynamic template handing with Knockout.js

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

Answers (1)

Callum Linington
Callum Linington

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

Related Questions