Knockout.js: Sum grouped values within table and foreach
Asked Answered
W

1

6

The table with the data-bindings currently looks like below:

Source        Calls     ChargeableCalls

Car Insurance
08434599111     3            2
08934345122     2            1

Home Insurance
08734599333     3            2
08034345555     2            1

The desired output should be like in the bellow example, The table should contain total values for Calls and ChargeableCalls grouped by Division, and total values for all Calls and ChargeableCalls within the table.

Source          Calls         ChargeableCalls

Car Insurance
08434599154       3                  2
08934345555       2                  1
Total Calls       5     Total CC     3

Home Insurance
08434599154       6                  3
08934345555       1                  0
Total Calls       7     Total CC     3

Total Calls All  24     Total CC All 12

Here are the bindings within the table:

<table class="table table-condensed" id="reportData">
<thead>
    <tr>
        <th>Source</th>
        <th>TotalCalls</th>
        <th>ChargeableCalls</th>
    </tr>
</thead>
<tbody data-bind="foreach: groups">
     <!-- ko foreach: $root.getGroup($data) -->
    <tr data-bind="if: $index() == 0">
        <td colspan="3" data-bind="text: division" class="division"></td>
    </tr>
    <tr>
        <td data-bind="text: source"></td>
        <td data-bind="text: totalCalls"></td>
        <td data-bind="text: chargeableCalls"></td>
    </tr>
    <!-- /ko -->
</tbody>

Here's my ViewModel:

function GroupedReportingViewModel() {
    var self = this;

    self.results = ko.observableArray();

    self.groupedResults = {};

    self.getGroup = function (group) {
       return self.groupedResults[group];
    };

    self.groupedResultsC = ko.computed(function () {
        self.groupedResults = {};
        ko.utils.arrayForEach(self.results(), function (r) {
           if (!self.groupedResults[r.division]) self.groupedResults[r.division] = [];
                        self.groupedResults[r.division].push(r);
                    });
           return self.groupedResults;
                });

     self.groups = ko.computed(function () {
         var g = [];
         for (x in self.groupedResultsC())
                g.push(x);
                return g;_
       });
    }

     var model = new GroupedReportingViewModel();
     ko.applyBindings(model);

The results observableArray gets populated from an ajax response, like below:

success: function (jsondata) {
        model.results(jsondata["Data"]["Report"]);
}

The jsondata object looks like below:

{"Data":
    {"Report":[ {"source":"08434599494","division":"Car Insurance","totalCalls":5, "chargeableCalls":23},
                {"source":"08434599172","division":"Car Insurance","totalCalls":512,"chargeableCalls":44},
                {"source":"08434599129","division":"Home  Insurance","totalCalls":4, "chargeableCalls":2},
                {"source":"08434599215","division":"Home Insurance","totalCalls":234, "chargeableCalls":54},
                {"source":"08434599596","division":"Car Insurance","totalCalls":332, "chargeableCalls":266}
              ]
    }
}

Q: How can I achieve the desired output?

Wreathe answered 19/3, 2015 at 15:38 Comment(0)
D
6

In your example, groupedResults is a list of arrays. Instead of this, try making a ViewModel for a group. This ViewModel can then be used for working out the totals. For example...

function GroupViewModel(division) {
    var self = this;
    self.Division = division;
    self.Items = ko.observableArray();
    self.Count = ko.computed(function() { 
        var count = 0;
        ko.utils.arrayForEach(self.Items(), function(r) { count += r.totalCalls; });
        return count; 
    });
    self.ChargeableCount = ko.computed(function() { 
        var count = 0;
        ko.utils.arrayForEach(self.Items(), function(r) { count += r.chargeableCalls; });
        return count; 
    });
}

You can simplify your main Viewmodel too, and push the items into the GroupViewModel instead:

function GroupedReportingViewModel() {
    var self = this;
    self.results = ko.observableArray();
    self.groupedResults = ko.computed(function() {
        var groups = [];
        ko.utils.arrayForEach(self.Results(), function(r) {
            var g = ko.utils.arrayFirst(groups, function(g) { return g.Division === r.division; });
            if (!g) {
                g = new GroupViewModel(r.division);
                groups.push(g);
            }
            g.Items.push(r);
        });
        return groups;
    });
    self.TotalCount = ko.computed(function() { 
        var count = 0;
        ko.utils.arrayForEach(self.results(), function(r) { count += r.totalCalls; });
        return count; 
    });
    self.TotalChargeableCount = ko.computed(function() { 
        var count = 0;
        ko.utils.arrayForEach(self.results(), function(r) { count += r.chargeableCalls; });
        return count; 
    });
}

Finally in your view, iterate through the groups, and then the items:

<tbody>
    <!-- ko foreach: groupedResults -->
    <tr>
        <td colspan="3" data-bind="text: Division" class="division"></td>
    </tr>
    <!-- ko foreach: Items -->
    <tr>
        <td data-bind="text: source"></td>
        <td data-bind="text: totalCalls"></td>
        <td data-bind="text: chargeableCalls"></td>
    </tr>
    <!-- /ko -->
    <tr>
        <td>Total Calls</td>
        <td data-bind="text: Count"></td>
        <td>Total Chargeable:</td>
        <td data-bind="text: ChargeableCount"></td>
    </tr>
    <!-- /ko -->
    <tr>
        <td>Total Calls All</td>
        <td data-bind="text: TotalCount"></td>
        <td>Total Chargeable All</td>
        <td data-bind="text: TotalChargeableCount"></td>
    </tr>
</tbody>
Donelladonelle answered 19/3, 2015 at 15:49 Comment(5)
Sorry, one last criticism, groupedResults must be an array. You can't foreach on an object.Clinkscales
Fixed. If the dataset were large I'd make it more efficient, but this is simple.Donelladonelle
It works great, thanks mate! Adding the total all values for Total Calls and Total Chargeable Calls within the table would require an additional ViewModel?Wreathe
No, just add a computed on the top level viewmodelDonelladonelle
Thanks for the update. Can you tell me how will this perform if the dataset were to increase to large over time?Wreathe

© 2022 - 2024 — McMap. All rights reserved.