Search code examples
javascript.netasp.net-mvcknockout.jsknockout-mapping-plugin

Knockout group list into smaller lists with objects


I have daily data for multiple employees and depending on the start time and end time that could mean a lot of data.

So with the mapping plugin i mapped them into one big list, but i will need them grouped by employee into smaller lists so i can make a tables per employee (like smaller view models) that has filtering and sorting for that subset of data.

Here is a basic example i created with static data.

$(function () {
    var data = {
        Employees: [{
            Id: 1,
            Name: "Employee1",
            Day: new Date(),
            Price: 12.54
        }, {
            Id: 2,
            Name: "Employee2",
            Day: new Date(),
            Price: 112.54
        }, {
            Id: 1,
            Name: "Employee1",
            Day: new Date(),
            Price: 12.54
        }, {
            Id: 3,
            Name: "Employee3",
            Day: new Date(),
            Price: 12.54
        }]
    };

    // simulate the model to json conversion. from now on i work with the json
    var jsonModel = JSON.stringify(data);

    function employeeModel(data) {
        var employeeMapping = {
            'copy': ["Id", "Name", "Day", "Price"]
        };
        ko.mapping.fromJS(data, employeeMapping, this);
    }

    function employeeViewModel(data) {
        var self = this;
        var employeesMapping = {
            'Employees': {
                create: function (options) {
                    return new employeeModel(options.data);
                }
            }
        };
        ko.mapping.fromJSON(data, employeesMapping, self);
    }

    var productsModel = new employeeViewModel(jsonModel);
    ko.applyBindings(productsModel);
});
table {
    border-collapse: collapse;    
}
table, th, td {
    border: 1px solid black;
}
tr:nth-child(even) {
    background-color: white;
}
tr:nth-child(odd) {
    background-color: #C1C0C0;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<table>
  <tbody data-bind="foreach: Employees">
    <tr>
      <td><span data-bind="text:Id"></span>
      </td>
      <td><span data-bind="text:Name"></span>
      </td>
      <td><span data-bind="text:Day"></span>
      </td>
      <td><span data-bind="text:Price"></span>
      </td>
    </tr>
  </tbody>
</table>


Solution

  • One possibility would be to use a computed value to group your data.

    self.EmployeeGroups = ko.pureComputed(function () {
        var employees = self.Employees(),
            index = {},
            group = [];
    
        ko.utils.arrayForEach(employees, function(empl) {
            var id = ko.unwrap(empl.Id);
            if ( !index.hasOwnProperty(id) ) {
                index[id] = {
                    grouping: {
                        Id: empl.Id,
                        Name: empl.Name
                    },
                    items: []
                };
                group.push(index[id]);
            }
            index[id].items.push(empl);
        });
    
        return group;
    });
    

    would turn your data from a flat array to this:

    [{
        grouping: {
            Id: /* ... */, 
            Name: /* ... */
        }
        items: [/* references to all employee objects in this group */]
    }, {
        /* same */
    }]
    

    Expand the code snippet below to see it at work.

    $(function () {
        var data = {
            Employees: [{
                Id: 1,
                Name: "Employee1",
                Day: new Date(),
                Price: 12.54
            }, {
                Id: 2,
                Name: "Employee2",
                Day: new Date(),
                Price: 112.54
            }, {
                Id: 1,
                Name: "Employee1",
                Day: new Date(),
                Price: 12.54
            }, {
                Id: 3,
                Name: "Employee3",
                Day: new Date(),
                Price: 12.54
            }]
        };
    
        var jsonModel = JSON.stringify(data);
    
        function employeeModel(data) {
            var employeeMapping = {
                'copy': ["Id", "Name", "Day", "Price"]
            };
            ko.mapping.fromJS(data, employeeMapping, this);
        }
    
        function employeeViewModel(data) {
            var self = this;
    
            self.Employees = ko.observableArray();
            self.EmployeeGroups = ko.pureComputed(function () {
                var employees = self.Employees(),
                    index = {},
                    group = [];
    
                ko.utils.arrayForEach(employees, function(empl) {
                    var id = ko.unwrap(empl.Id);
                    if ( !index.hasOwnProperty(id) ) {
                        index[id] = {
                            grouping: {
                                Id: empl.Id,
                                Name: empl.Name
                            },
                            items: []
                        };
                        group.push(index[id]);
                    }
                    index[id].items.push(empl);
                });
    
                return group;
            });
    
            // init
            var employeesMapping = {
                'Employees': {
                    create: function (options) {
                        return new employeeModel(options.data);
                    }
                }
            };
            ko.mapping.fromJSON(data, employeesMapping, self);
        }
    
        var productsModel = new employeeViewModel(jsonModel);
        ko.applyBindings(productsModel);
    });
    table {
        border-collapse: collapse;    
    }
    table, th, td {
        border: 1px solid black;
    }
    tr:nth-child(even) {
        background-color: #efefef;
    }
    tr:nth-child(odd) {
        background-color: #CCCCCC;
    }
    tr.subhead {
        background-color: #D6E3FF;
        font-weight: bold;
    }
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
    <table>
      <!-- ko foreach: EmployeeGroups -->
      <tbody>
        <!-- ko with: grouping -->
        <tr class="subhead">
          <td colspan="2">
            <span data-bind="text: Id"></span>
            <span data-bind="text: Name"></span>
          </td>
        </tr>
        <!-- /ko -->
        <!-- ko foreach: items -->
        <tr>
          <td><span data-bind="text: Day"></span></td>
          <td><span data-bind="text: Price"></span></td>
        </tr>
        <!-- /ko -->
      </tbody>
      <!-- /ko -->
    </table>
    
    <pre data-bind="text: ko.toJSON($root, null, 2)" style="font-size: smallest;"></pre>