Search code examples
knockout.jsdurandal

KO computed inside of observable array only computes on page load, and not after that


This is inside a Durandal app. I've got an observable array called Items, and each item has some properties and a ko.computed value of linetotal.

    self.items = ko.observableArray();
    self.item = function(data) {
        this.sku = ko.observable(data.sku);
        this.description = ko.observable(data.description);
        this.msrp = ko.observable(data.msrp)
        this.sell_price = ko.observable(data.sell_price);
        this.quantity = ko.observable(data.quantity);
        this.tax = ko.observable(data.tax);
        this.linetotal = ko.computed(function() {
            return Number(this.sell_price()) * Number(this.quantity());
        }, this);
    }

    self.activate = function () {
        //make an empty item row
        self.items.push(new self.item({
            sku: "",
            description: "",
            msrp: "",
            sell_price: "2.41",
            quantity: "3",
            tax: "",
        }));

    }

On page activation, I get the expected value of 7.23 in the line total field:

        <tbody data-bind="foreach: items">
          <tr>
            <td><input type="text" data-bind="attr: { value: $data.sku }"></td>   
            <td><input type="text" data-bind="attr: { value: $data.description }"></td>               
            <td><input type="text" data-bind="attr: { value: $data.msrp }"></td>
            <td><input type="text" data-bind="attr: { value: $data.sell_price }"></td>
            <td><input type="text" data-bind="attr: { value: $data.quantity }"></td>
            <td><input type="text" data-bind="attr: { value: $data.tax }"></td>
            <td data-bind="text: $data.linetotal"></td>           
          </tr>
        </tbody>

But it appears that the computed function is not bound to observables, since updating the quantity or sell_price fields does not change the computed value. I can post a js fiddle, but it won't be functional since this is inside of a Durandal app.

Here it is: https://jsfiddle.net/1zcfz1ax/

Am I approaching the problem wrong?


Solution

  • Use the value binding, not attr: { value: foo }

    Here's a cleaned up fiddle.

    I've pulled the Item "class" out of the viewmodel since it doesn't need to be there, removed the superfluous $data.foo bindings in favor of simply foo, changed the values to regular numbers rather than strings, so you don't have to cast them to numbers later.