Search code examples
jqueryknockout.jsknockout-mapping-pluginknockout-templating

Using nested knockout templates to display nested data


I am trying to make a table with collapsible rows using knockout. Each "parent" row exists at the top level of my data structure, and each "child" row is an element of a member array.

The problem comes when I update the data from the server. The data updates, but I can tell it is replacing my DOM objects because a checkbox in the "parent" row is getting cleared. I have a fiddle that demonstrates this problem, using a timer to update the data:

http://jsfiddle.net/jdlogicman/m2LWk/

I have read that using a nested foreach instead of {{each}} should fix it, but I can't get that to work - please see the commented-out section in the above fiddle.

If any existing question addresses this, I couldn't tell how. Any help is appreciated


Solution

  • You are not storing the value of the checkbox so it does get cleared each time. Does the fiddle below solve your problem?

    http://jsfiddle.net/unklefolk/RYqTT/

    <table>
        <thead>
            <th class="checkbox_th"/>
            <th class="name_th" align="left">Sample</th>
            <th class="status_th" align="left">Status</th>
        </thead>
        <tbody data-bind="template: {name: 'sampleRowTemplate', foreach: samples}"></tbody>
        <script type="text/x-jquery-tmpl" id="sampleRowTemplate">
            <tr>
                <td><input type="checkbox" name="select_sample" data-bind="checked: is_checked"/></td>
                <td><span data-bind='text: sample_name'/>   The checkbox =<span data-bind="text:is_checked"/></td>
                <td><span data-bind='text: ""'/><td/>
                </tr>
                    {{each sample_runs()}}
                    <tr>
                    <td/>
                    <td><span data-bind='text: $value.name'/></td>
                    <td><span data-bind='text: $value.status'/></td>
                </tr>
                    {{/each}}
                    <!--
                    <script type="text/x-jquery-tmpl" id="sampleRunTemplate">
                    <td/>
                    <td>${name}</td>
                    <td>${status}</td>
                </script>
                    <tr data-bind="template: {name: 'sampleRunTemplate', foreach: sample_runs  }"></tr>
                    -->
    
                </script>
                </table>            
    
    
    var mapping = {
        'samples': {
            key: function(item) {
                return ko.utils.unwrapObservable(item.id);
            },
            'sample_runs': {
                key: function(item) {
                    return ko.utils.unwrapObservable(item.id);
                }
            }
        }
    };
    
    var data = {};
    data.samples = [{
        id: "s1",
        sample_name: "AR008",
        is_checked: false,    
        sample_runs: [
            {
            id: "rs1",
            name: "run1",
            status: "done"}]}];
    var viewModel = ko.mapping.fromJS(data, mapping);
    ko.applyBindings(viewModel);
    
    // update the data every 2 seconds
    var i = 0;
    setInterval(function() {
        var data = {};
        data.samples = [{
            id: "s1",
            sample_name: "AR008" + i,
            sample_runs: [
                {
                id: "rs1",
                name: "run" + i,
                status: "done" + i}]}];
        // is this right? updateFromJS is deprecated
        viewModel = ko.mapping.fromJS(data, viewModel);
        i++;
    }, 2000);