Originally posted on: http://geekswithblogs.net/jtimperley/archive/2013/07/28/knockout.js---filtering-sorting-and-paging.aspxKnockout.js is fantastic! Maybe I missed it but it appears to be missing flexible filtering, sorting, and pagination of its grids. This is a summary of my attempt at creating this functionality which has been working out amazingly well for my purposes. Before you continue, this post is not intended to teach you the basics of Knockout. They have already created a fantastic tutorial for this purpose. You'd be wise to review this before you continue. http://learn.knockoutjs.com/ Please view the full source code and functional example on jsFiddle. Below you will find a brief explanation of some of the components. http://jsfiddle.net/JTimperley/pyCTN/13/ First we need to create a model to represent our records. This model is a simple container with defined and guaranteed members. function CustomerModel(data)
{
if (!data)
{
data = {};
}
var self = this;
self.id = data.id;
self.name = data.name;
self.status = data.status;
}
Next we need a model to represent the page as a whole with an array of the previously defined records. I have intentionally overlooked the filtering and sorting options for now. Note how the filtering, sorting, and pagination are chained together to accomplish all three goals. This strategy allows each of these pieces to be used selectively based on the page's needs. If you only need sorting, just sort, etc.
function CustomerPageModel(data)
{
if (!data)
{
data = {};
}
var self = this;
self.customers = ExtractModels(self, data.customers, CustomerModel);
var filters = […];
var sortOptions = […];
self.filter = new FilterModel(filters, self.customers);
self.sorter = new SorterModel(sortOptions, self.filter.filteredRecords);
self.pager = new PagerModel(self.sorter.orderedRecords);
}
The code currently supports text box and drop down filters. Text box filters require defining the current 'Value' and the 'RecordValue' function to retrieve the filterable value from the provided record. Drop downs allow defining all possible values, the current option, and the 'RecordValue' as before. Once defining these filters, they are automatically added to the screen and any changes to their values will automatically update the results, causing their sort and pagination to be re-evaluated.
var filters = [
{
Type: "text",
Name: "Name",
Value: ko.observable(""),
RecordValue: function(record) { return record.name; }
},
{
Type: "select",
Name: "Status",
Options: [
GetOption("All", "All", null),
GetOption("New", "New", true),
GetOption("Recently Modified", "Recently Modified", false)
],
CurrentOption: ko.observable(),
RecordValue: function(record) { return record.status; }
}
];
Sort options are more simplistic and are also automatically added to the screen. Simply provide each option's name and value for the sort drop down as well as function to allow defining how the records are compared.
This mechanism can easily be adapted for using table headers as the sort triggers. That strategy hasn't crossed my functionality needs at this point.
var sortOptions = [
{
Name: "Name",
Value: "Name",
Sort: function(left, right) { return CompareCaseInsensitive(left.name, right.name); }
}
];
Paging options are completely contained by the pager model.
Because we will be chaining arrays between our filtering, sorting, and pagination models, the following utility method is used to prevent errors when handing an observable array to another observable array.
function GetObservableArray(array)
{
if (typeof(array) == 'function')
{
return array;
}
return ko.observableArray(array);
}