Knockout group list into smaller lists with objects
Asked Answered
G

1

6

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>
Giblets answered 6/2, 2015 at 10:26 Comment(0)
T
2

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>
Tempestuous answered 6/2, 2015 at 11:0 Comment(7)
Yes i know that you can make the grouping like this, but i wasn't and still not sure if you can sort, filter and add other functionalities to the smaller lists. Is there any way you can do this with this code in knockout?Giblets
Of course you can do that in knockout. If you want a filtered group, simply add another computed value that filters the group.Tempestuous
But from what i understand the group is computed and you don't have the groupings in memory right? So if you want to sort a specific group you will have to group them again and then sort the specific set?Giblets
Knockout computeds cache their values. They do not re-calculate unless it's necessary (because one of the observables they depend on changes). So you could - for example - make a computed that outputs an array of EmployeeGroup viewmodels (instead of an array of plain objects, like I did). Then each of these viewmodels could have its own computeds that govern how their respective data is sorted or filtered.Tempestuous
plnkr.co/edit/vKHq0ZXkEvj8ocFZzrCj I've used your code as base for my issue, but i can't manage to make the sort work for the smaller subset of data. What am i doing wrong for the sorting part?Giblets
@Giblets Here is what I had in mind: plnkr.co/edit/KKOIjIdKUE5RvPcvTATa?p=previewTempestuous
thanks, this is the full answer to the question. I already made one solution based on your first answer, but this is better.Giblets

© 2022 - 2024 — McMap. All rights reserved.