Edit in JSFiddle

// sample data
var data = [
    {city:"Fort Dodge", zip:"34889"},
    {city:"Fitchburg", zip:"83366"},
    {city:"Menomonee Falls", zip:"52534"},
    {city:"Norfolk", zip:"02232"},
    {city:"Dearborn", zip:"55899"},
    {city:"Pomona", zip:"90483"},
    {city:"Port Orford", zip:"02565"},
    {city:"Clovis", zip:"98500"},
    {city:"Sister Bay", zip:"20285"},
    {city:"Bethlehem", zip:"57757"},
    {city:"Broken Arrow", zip:"18820"},
    {city:"Idabel", zip:"65964"},
    {city:"Macomb", zip:"43974"},
    {city:"Dearborn", zip:"03451"},
    {city:"Corinth", zip:"93401"},
    {city:"El Paso", zip:"26282"},
    {city:"Cortland", zip:"67010"},
    {city:"Barre", zip:"52555"},
    {city:"Aspen", zip:"62155"},
    {city:"New Brunswick", zip:"68095"},
    {city:"Garland", zip:"85242"},
    {city:"Gloucester", zip:"33586"},
    {city:"Beacon", zip:"21493"},
    {city:"New Rochelle", zip:"82251"},
    {city:"Fairbanks", zip:"66241"},
    {city:"Lewiston", zip:"38572"},
    {city:"Bismarck", zip:"91912"},
    {city:"Hammond", zip:"69642"},
    {city:"Uniontown", zip:"31508"},
    {city:"Rolla", zip:"92430"}

var getFakeJsonRequest = function(page, pageSize){
    var start = (page-1)*pageSize,
        end = start + pageSize;
    while(end > data.length){
    var array = data.slice(start,end);
    return {
        json: JSON.stringify(array),
        delay: 1

knockout-paged.js - A Pager Plugin for Knockout.JS
Written By: Leland M. Richardson

Desired API:

    .paged(Number pageSize);
        assumes static data, creates pager with given pageSize

    .paged(Number pageSize, String url);
        assumes `url` is an AJAX endpoint which returns the requested data with
        url parameters "pg" and "perPage"

    .paged(Object config);
        pass config object with optional + required parameters (most flexible)

    //todo: perhaps some "inf-scroll" type functionality?
    //todo: restful configuration?

    Object Configuration:
        pageSize: Number (10),
        cached: Boolean (true),

        url: String




    // module scope

    // UTILITY METHODSextend
    // ------------------------------------------------------------------
    var extend = ko.utils.extend;

    // escape regex stuff
    var regexEscape = function(str) {
        return (str + '').replace(/[\-#$\^*()+\[\]{}|\\,.?\s]/g, '\\$&');

    // simple string replacement
    var tmpl = function(str, obj) {
        for (var i in obj) {
            if (obj.hasOwnProperty(i) !== true) continue;

            // convert to string
            var value = obj[i] + '';

            str = str.replace(new RegExp('{' + regexEscape(i) + '}', 'g'), value);
        return str;

    // construct url with proper data
    var construct_url = function(template,pg,pageSize){
        var start = pageSize * (pg-1),
            end = start + pageSize,
            data = {pg: pg, pageSize: pageSize, start: start, end: end};
        return typeof template === 'function' ? template(data) : tmpl(template,data);

    //constructor mapping function...
    var cmap = function(array,Ctor){
        return $.map(array,function(el){
            return new Ctor(el)

    // constructs the config object for each isntance based on parameters
    var config_init = function(defaults,a,b,c){

        var cfg = extend({},defaults);

        if(typeof a === "number"){
            // pageSize passed as first param
            cfg.pageSize = a;

            if(typeof b === "string"){
                cfg.url = b;
                cfg.async = true;
        } else {

        // don't let user override success function... use mapFromServer instead
        if(cfg.ajaxOptions && cfg.ajaxOptions.success){
            console.log("'success' is not a valid ajaxOptions property.  Please look into using 'mapFromServer' instead.");
            delete cfg.ajaxOptions.success;

        return cfg;

    // ----------------------------------------------------------------------
    var _defaults = {
        pageSize: 10,
        async: false, //TODO: make best guess based on other params passed?

        // async only options
        // --------------------------------------------
        getPage: null,
        url: null, // this can be a string or a function ({pg: pg, pageSize: pageSize, start: start, end: end})

        ctor: null, //constructor to be used for

        // function to be applied on "success" callback to map
        // response to array. Signature: (Object response) -> (Array)
        mapFromServer: null,
        ajaxOptions: {}, //options to pass into jQuery on each request
        cache: true


    // -----------------------------------------------------------------------
    var paged = function(a,b){
        var items = this,
            hasInitialData = this().length > 0; // target observableArray

        // config initialization
        var cfg = config_init(_defaults,a,b),

        // current page
        current = ko.observable(1),

        pagedItems = ko.computed(function(){
            var pg = current(),
                start = cfg.pageSize * (pg-1),
                end = start + cfg.pageSize;
            return items().slice(start,end);

        // array of loaded
        var loaded = [true]; // set [0] to true just because.
            loaded[current()] = true;
        var isLoading = ko.observable(true);

        // next / previous / goToPage methods

        var goToPage = cfg.async ? function(pg){
            if(cfg.cache && loaded[pg]){
                //data is already loaded. change page in setTimeout to make async
            } else {
                // user has specified URL. make ajax request
                // ************************************************************
                // IMPORTANT: API IS MODIFIED HERE TO WORK WITH jsFiddle!!!!!!!
                // ************************************************************
                    url: '/echo/json/',
                    data: getFakeJsonRequest(pg, cfg.pageSize),
                    type: "POST",
                    success: function(res){
                        // allow user to apply custom mapping from server result to data to insert into array
                        var results;
                            results = cfg.mapFromServer(res);
                        } else {
                            //todo: check to see if res.data or res.items or something...
                            results = res;
                    complete: function() {
                        //todo: user could override... make sure they don't?  (use compose)

        } : current; // if not async, all we need to do is assign pg to current

        //maps new data to underlying array
        var onPageReceived = function(pg,data){
            // if constructor passed in, map data to constructor
            if(cfg.ctor !== null){
                data = cmap(data,cfg.ctor);
            // append data to items array
            var start = cfg.pageSize*(pg-1);

            if(cfg.cache) {loaded[pg] = true;}

        var next = function(){

        next.enabled = ko.computed(function(){
            //TODO: handle differently for ajax stuff
            return true || items().length > cfg.pageSize * current();

        var prev = function(){

        prev.enabled = ko.computed(function(){
            return current() > 1;

        // actually go to first page

        // exported properties
            current: current,
            pagedItems: pagedItems,
            pageSize: cfg.pageSize,
            isLoading: isLoading, // might not need this if not async?
            next: next,
            prev: prev,
            goToPage: goToPage,

            __paged_cfg: extend({},cfg) //might want to remove later


        // return target
        return items;
    // expose default options to be changed for users
    paged.defaultOptions = _defaults;

    //export to knockout
    ko.observableArray.fn.paged = paged;


var Example = function(){
    this.items = ko.observableArray().paged(4,'/url/which/wont/be/used/in/this/example');

ko.applyBindings(new Example());

    <div id="test" class="container">

        <table class="table table-bordered">
            <tbody data-bind="foreach: items.pagedItems">
                    <td data-bind="text: city"></td>
                    <td data-bind="text: zip"></td>

        <ul class="pager">
            <li data-bind="css: {'disabled': !items.prev.enabled()}">
                <a href="#" data-bind="click: items.prev">Previous</a>
            <li class="li-loader">
                    <span class="ajax-loader" data-bind="css: {active: items.isLoading}"></span>
            <li data-bind="css: {'disabled': !items.next.enabled()}">
                <a href="#" data-bind="click: items.next">Next</a>