Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout.js: Sum grouped values within table and foreach

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?

like image 561
alex Avatar asked Mar 19 '15 15:03

alex


1 Answers

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>
like image 164
Richard Avatar answered Nov 11 '22 16:11

Richard