Question

I have some problems regarding binding to computed value. I am showing data in table, and summing up some fields. In footer I want to display the sum of those fields, and the empty column, as well as one with text of total. The values get stored inside of fields, as well as updated when the value of the referenced fields changes, but it doesn't show in the view. I know that I did something wrong with ko.computed, but I can't pinpoint what would that be.

HTML code:

    <table>
    <caption><h4 class="pull-left">Caption</h4></caption>
    <thead>
        <tr data-bind="foreach: ColModel">
            <th data-bind="text: Caption"></th>
        </tr>
    </thead>
    <tbody data-bind="foreach: { data: model, as: 'row' }">
        <tr data-bind="foreach: { data: $root.ColModel, as: 'col' }">
            <td data-bind="text: row[col.Field], css: { hidden: col.InSum != false }">

            </td>
            <td data-bind="css: { hidden: col.InSum != true }">
                <input type="number" step="any" class="input-small" data-bind="value:row[col.Field], valueUpdate: 'afterkeydown'" />
            </td>
        </tr>
    </tbody>
    <tfoot>
        <tr data-bind="foreach: footmodel">
            <td data-bind="text: Value">
            </td>
        </tr>
    </tfoot>
</table>

Iin my JS I have

clientViewModel.ColModel = ko.observableArray([
    {
        Caption: 'cap1',
        InSum: false,
        Field: 'FLD1'
    },
    {
        Caption: 'cap2',
        InSum: false,
        Field: 'FLD2'
    },
    {
        Caption: 'capsum1',
        InSum: true,
        Field: 'fldsum1'
    },
    {
        Caption: 'capsum2',
        InSum: true,
        Field: 'fldsum2'
    }
]);     
clientViewModel.model = ko.observableArray();
clientViewModel.footmodel = ko.observableArray([
    {
        Value: '',
        IsSum: false
    },
    {
        Value: 'total',
        IsSum: false
    },
    {
        Value: '',
        IsSum: true,
        SumField: 'fldsum1'    
    },
    {
        Value: '',
        IsSum: true,
        SumField: 'fldsum2'
    }
]);

ajax function is just a tiny wrapper around $.ajax function which we use in project to fetch data.

ajax('modelurl'),
    'GET',
    true,
    function (data, status) {
        $.each(data.Records, function (index, value) {
            var observableValue = makeObservable(value, clientViewModel.ColModel);
            clientViewModel.model.push(observableValue);
        });
        $.each(clientViewModel.footmodel(), function (index, value) {
            if (value['IsSum']) {
                clientViewModel.footmodel()[index]['Value'] = ko.computed(function () {
                    var sum = 0;
                    $.each(clientViewModel.model(), function (i, data) {
                        sum = parseFloat(sum) + parseFloat(data[value['SumField']]());
                    });
                    return sum.toString();
                }, clientViewModel);
            }
            else
                value['Value'] = ko.observable(value['Value']);
        });
});

Also, makeObservable is used to make the fields of an object observable. As I found out, all the fields that are connected to computed should be observable, thus the update can trigger.

function makeObservable(model, fields) {
    var ret = {};
    $.each(fields(), function (index, value) {
        ret[value.Field] = ko.observable(model[value.Field]);
    })
    return ret;
}

What is going on is unknown to me. Observable fields of the footmodel get printed out, while computed don't. All the fields of clientViewModel.model observable array are observable, the values get calculated, but they don't get printed out. What is the reason for that?

One possible solution, as far as I see it, could be to trigger the function that calculates the values to be summed and stores it in the observable field, that should get printed out OK. But I would rather make it work this way, and find out what causes this behavior.

Thanks.

Was it helpful?

Solution

I guess you init ko.applyBindings(...) outside the ajax call, which means

<tr data-bind="foreach: footmodel">
    <td data-bind="text: Value"> 
    </td>
</tr>

The text: Value binds to the original empty string, even you replace the content of the Value in ajax with a ko.computed(...), the binding system doesn't know.

To fix it, use an if binding to force knockout to re-parse the binding (the whole table) after ajax.

HTML

<!-- ko ifnot: loading -->
  <table> ... </table>
<!-- /ko -->

JS

clientViewModel.loading = ko.observable(false);

// begin ajax
clientViewModel.loading(true); // ko removes the table from DOM

$.ajax({...,
  success: function(data, status) {
   //... built your ko.computed
  },
  complete: function() {
   // finish ajax
   clientViewModel.loading(false); // ko re-creates the table in DOM
  }
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top