Knockout - Filtering, Sorting, and Paging

by kougiland

HTML

<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<b>Filters:</b><br />
<div data-bind="foreach: filter.filters">
    <div>
        <span data-bind="text: Name"></span>:<br />
    </div>
    <div data-bind="if: Type == 'select'">
        <select data-bind="options: Options, optionsText: 'Name', value: CurrentOption"></select>
    </div>
    <div data-bind="if: Type == 'text'">
        <input type="text" data-bind="value: Value, valueUpdate: 'afterkeydown'" />
    </div>
</div>
<br />
<b>Sorts:</b>
Field:<br />
<select data-bind="options: sorter.sortOptions, optionsText: 'Name', value: sorter.currentSortOption"></select>
Direction:
<select data-bind="options: sorter.sortDirections, optionsText: 'Name', value: sorter.currentSortDirection"></select>
<br />
<br />

<div class="Pager"></div>
<div class="NoRecords"></div>
<table>
    <thead>
        <tr>
            <th>
                ID
            </th>
            <th>
                Name
            </th>
            <th>
                Status
            </th>
        </tr>
    </thead>
    <tbody data-bind="foreach: pager.currentPageRecords">
        <tr>
            <td>
                <span data-bind="text: id"></span>
            </td>
            <td>
                <span data-bind="text: name"></span>
            </td>
            <td>
                <span data-bind="text: status"></span>
            </td>
        </tr>
    </tbody>
</table>

CSS

.Pager
{
    background-color: LightGray;
}
a
{
    margin: 0px 5px;
}

JavaScript

function CustomerModel(data)
{
	if (!data)
	{
		data = {};
	}
	
	var self = this;
	self.id = data.id;
	self.name = data.name;
	self.status = data.status;
}

function CustomerPageModel(data)
{
	if (!data)
	{
		data = {};
	}
	
	var self = this;
	self.customers = ExtractModels(self, data.customers, CustomerModel);
	
	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("None", "None", "None"),
				GetOption("New", "New", "New"),
				GetOption("Recently Modified", "Recently Modified", "Recently Modified")
			],
			CurrentOption: ko.observable(),
			RecordValue: function(record) { return record.status; }
		}
	];
	var sortOptions = [
		{
			Name: "ID",
			Value: "ID",
			Sort: function(left, right) { return left.id < right.id; }
		},
        {
			Name: "Name",
			Value: "Name",
			Sort: function(left, right) { return CompareCaseInsensitive(left.name, right.name); }
		},
		{
			Name: "Status",
			Value: "Status",
			Sort: function(left, right) { return CompareCaseInsensitive(left.status, right.status); }
		}
	];
	self.filter = new FilterModel(filters, self.customers);
	self.sorter = new SorterModel(sortOptions, self.filter.filteredRecords);
	self.pager = new PagerModel(self.sorter.orderedRecords);
}

function PagerModel(records)
{
	var self = this;
	self.pageSizeOptions = ko.observableArray([1, 5, 25, 50, 100, 250, 500]);
	
	self.records = GetObservableArray(records);
	self.currentPageIndex = ko.observable(self.records().length > 0 ? 0 : -1);
	self.currentPageSize = ko.observable(25);
	self.recordCount = ko.computed(function() {
		return self.records().length;
	});
	self.maxPageIndex = ko.computed(function() {
		return Math.ceil(self.records().length / self.currentPageSize()) - 1;
	});
	self.currentPageRecords = ko.computed(function() {
		var newPageIndex = -1;
		var pageIndex...