Knockout Observable Array Paging Extender

A KnockoutJs extender for adding paging support to observable arrays.

by mrrodd

HTML

<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js"></script>
<div style="float:left; border: 1px solid black;">
    Paged Array
    <ul data-bind="foreach : items.currentPageData">
        <li data-bind="text: value"></li>
    </ul>
</div>
<div style="float:left; border: 1px solid blue; margin-left: 20px; margin-bottom: 5px;">
    Original Array
    <ul data-bind="foreach: items">
        <li data-bind="text: value"></li>
    </ul>
</div>
<div style="clear: both"></div>
<a href="#" data-bind="click: items.moveFirst">First</a>
<a href="#" data-bind="click: items.movePrevious">Previous</a>
<a href="#" data-bind="click: items.moveNext">Next</a>
<a href="#" data-bind="click: items.moveLast">Last</a>

Page    
<input type="number" data-bind="value: items.currentPage, attr: { 'max': items.pageCount }" min="1">
    of <span data-bind="text: items.pageCount"></span>

CSS

body { font-family: arial; }
div { padding: 4px;

JavaScript

ko.extenders.paging = function (target, pageSize) {
    var _pageSize = ko.observable(pageSize || 10), // default pageSize to 10
        _currentPage = ko.observable(1); // default current page to 1

    target.pageSize = ko.computed({
        read: _pageSize,
        write: function (newValue) {
            if (newValue > 0) {
                _pageSize(newValue);
            }
            else {
                _pageSize(10);
            }
        }
    });

    target.currentPage = ko.computed({
        read: _currentPage,
        write: function (newValue) {
            if (newValue > target.pageCount()) {
                _currentPage(target.pageCount());
            }
            else if (newValue <= 0) {
                _currentPage(1);
            }
            else {
                _currentPage(newValue);
            }
        }
    });

    target.pageCount = ko.computed(function () {
        return Math.ceil(target().length / target.pageSize()) || 1;
    });

    target.currentPageData = ko.computed(function () {
        var pageSize = _pageSize(),
            pageIndex = _currentPage(),
            startIndex = pageSize * (pageIndex - 1),
            endIndex = pageSize * pageIndex;

        return target().slice(startIndex, endIndex);
    });

    target.moveFirst = function () {
        target.currentPage(1);
    };
    target.movePrevious = function () {
        target.currentPage(target.currentPage() - 1);
    };
    target.moveNext = function () {
        target.currentPage(target.currentPage() + 1);
    };
    target.moveLast = function () {
        target.currentPage(target.pageCount());
    };

    return target;
};
/// -- End paging extender -- \\\



/// Sample View Model that has an observableArray extended with the paging extender
var ViewModel = function () {
    this.items = ko.observableArray([{value: '1'},{value: '2'},
                                     {value: '3'},{value: '4'},
                                     {value:...