javascriptjsonknockout.jsko.observablearraycomputed-observable

Knockoutjs computed variables from json filled data


i'm currently experimenting a bit with knockout and javascript in general but just hit a problem I can't solve.

I have a viewmodel with several (simple, since this is just for testing) sub VMs and one of them has a computed variable which should display the aggregated costs of an array of another VM.

When I load the data via json everything gets populated nicely but the computed remains "undefined". I already tried flagging the computed with "deferEvaluation" but that didn't help.

To make it short here is my jsfiddle http://jsfiddle.net/v73rxckf/9/

Any Ideas? Or am I doing something wrong in general?

JS Code:

function Service(data) {
    var self = this;

    self.Cost = ko.observable(data.Cost);
}

function Action(data) {
    var self = this;

    self.Name = ko.observable(data.Name);
    self.Dates = ko.observableArray(data.Dates);
    self.Services = ko.observableArray(data.Services);

    self.OverallCost = ko.computed(function() {
        calculated = 0;

        ko.utils.arrayForEach(self.Services(), function(item) {
            calculated += parseFloat(ko.utils.unwrapObservable(item.Cost));
        });

        return calculated;
    }, { deferEvaluation : true });

}

function Campaign(data) {

    var self = this;

    self.Name               = ko.observable(data.Name);
    self.Deadline           = ko.observable(data.Deadline);
    self.ActionList         = ko.observableArray(data.ActionList);
}

function ApplicationViewModel () {

    var self = this;
    self.List = ko.observableArray([]);

    $.ajax("/echo/json/", {
        data: {
            json: ko.toJSON(fakeData)
        },
        type: "POST",
        dataType: 'json',
        success: function(data) {
            var mappedCampaigns = $.map(data, function(item) {
                return new Campaign(item);
            });

            self.List(mappedCampaigns);
        }
    });
}

var fakeData = [{
    "Name":"testname1",
    "Deadline":"testdeadline1",
    "ActionList":[
                    {
                        "Name":"testaction1",
                        "Dates":[ { "DateValue":"testdate1" }, { "DateValue":"testdate2" } ],
                        "Services":[ { "Cost":"100" }, { "Cost":"50" } ]
                    },
                    {
                        "Name":"testaction2",
                        "Dates":[ { "DateValue":"testdate1" }, { "DateValue":"testdate2" } ],
                        "Services":[{ "Cost":"100" }, { "Cost":"50" } ]
                    }
                ]
}];

ko.applyBindings(new ApplicationViewModel());

Solution

  • You are not initializing your Action objects, in your campaign view model you need to new-up your action view models and push them into the observable array:

    function Campaign(data) {
    
        var self = this;
    
        self.Name               = ko.observable(data.Name);
        self.Deadline           = ko.observable(data.Deadline);
        self.ActionList         = ko.observableArray([]);
    
        if(data.ActionList){
            for(var i = 0; i < data.ActionList.length; i++){
                self.ActionList.push(new Action(data.ActionList[i]));
            }
        }
    }
    

    You will need to do the same thing for your Service view model as well. e.g.:

    function Service(data) {
        var self = this;
    
        self.Cost = ko.observable(data.Cost);
    }
    
    function Action(data) {
        var self = this;
    
        self.Name = ko.observable(data.Name);
        self.Dates = ko.observableArray(data.Dates);
        self.Services = ko.observableArray([]);
    
        if(data.Services){
            for(var i = 0; i < data.Services.length; i++){
                self.Services.push(new Service(data.Services[i]));
            }
        }
    
        self.OverallCost = ko.computed(function() {
            calculated = 0;
    
            ko.utils.arrayForEach(self.Services(), function(item) {
                calculated += parseFloat(ko.utils.unwrapObservable(item.Cost));
            });
    
            return calculated;
        });
    
    }