alex
alex

Reputation: 1350

KnockoutJS observableArray: group data in foreach

The table with the knockout.js bindings currenlty looks lke this:

source   total   division
 00234   4506     div1
 30222    456     div2
 63321     23     div2
 40941    189     div1

The desired output would be something like below. The data needs to grouped by division.

source   total   
div1
 00234   4506 
 40941    189 
div2
 30222    456
 63321     23

Here's my ViewModel:

var ReportingViewModel;
ReportingViewModel = { Results: ko.observableArray(null) }

The ReportingViewModel gets populated via an ajax request:

ReportingViewModel.Results(data["Data"]["Report"]);

Q: How can I achieve the desired output?

EDIT:
Here's my View:

        <table class="table table-condensed" id="reportData">
            <thead>
                <tr>
                    <th>source</th>
                    <th>total</th>
                    <th>division</th>   
                </tr>
            </thead>
            <tbody data-bind="foreach: Results">
                <tr>
                    <td data-bind="text: source"></td>
                    <td data-bind="text: total"></td>
                    <td data-bind="text: division"></td>
                </tr>
            </tbody>
        </table>

<script type="text/javascript">

    $(document).ready(function () {

            ReportingViewModel.Results(null);
            e.preventDefault();
            var numbers = null;
            if ($('#numbersdd').find("option:selected").length > 0) {
                numbers = $('#numbersdd').find("option:selected");}

            if (numbers != null) {

                    $.ajax({
                        url: '/Reporting/ReportData.aspx',
                        type: 'POST',
                        data: numbers,
                        dataType: 'json',
                        contentType: "application/json",
                        success: function (data) {
                            ReportingViewModel.Results(data["Data"]["Report"]);
                        },
                        error: function () {
                            alert('Error Running Report');
                        }
                    });
            }
            else { alert('No Data!'); }
        });

        var ReportingViewModel;

        ReportingViewModel = {
            Results: ko.observableArray(null),              
        }
        ko.applyBindings(ReportingViewModel);
   });

</script>

Upvotes: 1

Views: 3084

Answers (2)

Alex Kopachov
Alex Kopachov

Reputation: 733

You may declare computed field like this:

GroupedResults: ko.computed(function() {
    var result = {};
    var original = ReportingViewModel.Results();
    for (var i = 0; i < original.length; i++) { 
        var item = original[i];
        result[item.division] = result[item.division] || []; 
        result[item.division].push(item); 
    }

    return result;
})

This computed field will return object like this:

{
    div1: [{source: 234, total: 4506, division: 'div1'}]
    div2: [{source: 30222, total: 456, division: 'div2'}]
}

As you can see each property is a division and it contains array of records which are related to this division.

Then just bind your view to this new computed field.

If you want to create your computed as part of your ReportingViewModel declaration, do it like this:

var ReportingViewModel = function(data) {
    var self = this;

    self.Results = ko.observableArray(data);

    self.GroupedResults = ko.computed(...)
}

Then your invocation of the object is similar to how you currently have it...but not.

var reportingViewModel = new ReportingViewModel(data["Data"]["Report"]);
ko.applyBindings(reportingViewModel);

Upvotes: 2

beauXjames
beauXjames

Reputation: 8418

Here is a reasonable fiddle on grouping data in Knockout 2.0 that should work for you.

http://jsfiddle.net/rniemeyer/mXVtN/

Most importantly, you should be transforming your data so you have your divisions as an element to loop through and each division has a computed child that returns the matching data. He happens to use an extension on the observableArray property itself to manage this...

ko.observableArray.fn.distinct = function(prop) {
    var target = this;
    target.index = {};
    target.index[prop] = ko.observable({});    

    ko.computed(function() {
        //rebuild index
        var propIndex = {};

        ko.utils.arrayForEach(target(), function(item) {
            var key = ko.utils.unwrapObservable(item[prop]);
            if (key) {
                propIndex[key] = propIndex[key] || [];
                propIndex[key].push(item);            
            }
        });   

        target.index[prop](propIndex);
    });

    return target;
};    

Then in your markup just data-bind to loop through your divisions.

Upvotes: 1

Related Questions