Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockoutjs. How to compute data changed inside observable array

Please, look at my text. I try to use observableArray of knockoutjs and foreach to compute data of array. Example 1 works fine: total sum computed if you change data in the fields. But Example 2 is not working.

<html>
<head>                                                                         
    <title></title>                                                            
    <script type='text/javascript' src='/js/jquery-1.8.2.min.js'></script>     
    <script type='text/javascript' src='/js/knockout-2.1.0.debug.js'></script> 
</head>                                                                        
<body>
<p>Example 1</p>
<div>
    <p>
    <input data-bind="value: fnum1" />
    <input data-bind="value: fnum2" />
    <span data-bind="text: ftotsum"></span>
    </p>
</div>    
<p>Example 2</p>
<div>
    <p>
    <!-- ko foreach: fields -->
    <input data-bind="value: $data" />
    <!-- /ko -->
    <span data-bind="text: ltotsum"></span>
    </p>
</div>
</body>
<script>
    function vm(){
        //Calc Example 1
        var self = this;
        self.fnum1 = ko.observable(1);
        self.fnum2 = ko.observable(2);
        self.ftotsum = ko.computed(function(){
            return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
        });
        //Calc Example 2
        self.fields = ko.observableArray([1, 2]);
        self.ltotsum = ko.computed(function(){
            var total = 0;
            ko.utils.arrayForEach(self.fields(), function(item) {
                total += parseFloat(item);
            })
            return total;
        }); 
    };

    ko.applyBindings(new vm());
</script>
</html>
like image 493
vkim Avatar asked Oct 01 '12 18:10

vkim


2 Answers

EDIT: Got fiddle working, Raffaele is correct in saying you need to wrap the observable inside an object, but you can do it within the array creation itself and I like to use the ko.utils to unwrap my observables, it does the same thing for observables but it won't crash if there is a non-observable passed to it. See fiddle for full example.

An observableArray doesn't make the values passed observable, this is a common mistake. An observableArray just observes the modifications to the array and not the values. If you want to have your values inside your array be observable you have to make them so.

function vm(){
    //Calc Example 1
    var self = this;
    self.fnum1 = ko.observable(1);
    self.fnum2 = ko.observable(2);
    self.ftotsum = ko.computed(function(){
        return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
    });
    //Calc Example 2
    self.fields = ko.observableArray([{"num":ko.observable(1)},{"num":ko.observable(2)}]);
    self.ltotsum = ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(self.fields(), function(item) {
            total += parseFloat(ko.utils.unwrapObservable(item.num));
        });
        return total;
    }); 
};
ko.applyBindings(new vm());

Should work with the example above now.

like image 197
Rynan Avatar answered Sep 23 '22 22:09

Rynan


The documentation says:

Key point: An observableArray tracks which objects are in the array, not the state of those objects

Simply putting an object into an observableArray doesn’t make all of that object’s properties themselves observable. Of course, you can make those properties observable if you wish, but that’s an independent choice. An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed.

Your second example doesn't work because the value of the input fields is not bound to the values in the array. That values in the array are used only once, in the foreach binding, but when you type in the input boxes, nothing triggers KO.

Here is a working fiddle with a solution implemented. I used a helper ObsNumber

function vm(){
    var self = this;

    var ObsNumber = function(i) {
        this.value = ko.observable(i);
    }

    self.fields = ko.observableArray([new ObsNumber(1) ,
                                      new ObsNumber(2)]);

    self.sum = ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(self.fields(), function(item) {
            total += parseFloat(item.value());
        });
        return total;
    });
};

ko.applyBindings(new vm());

and the following markup

<div>
    <p>
    <!-- ko foreach: fields -->
    <input data-bind="value: $data.value" />
    <!-- /ko -->
    <span data-bind="text: sum"></span>
    </p>
</div>​
like image 22
Raffaele Avatar answered Sep 22 '22 22:09

Raffaele