KnockoutJS Paged Editable Grid

Paging a Knockout Observable Array

by mrrodd

HTML

<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div id="grid" class="container">
    <div class="row">
        <div class="col-md-6">
            <table class="table table-condensed table-striped">
                <thead>
                    <tr>
                        <th><label class="control-label">Row</label></th>
                        <th><label class="control-label">City</label></th>
                        <th><label class="control-label">Zip</label></th>
                    </tr>
                </thead>
                <tbody data-bind="foreach: pagedItems">
                    <tr>
                        <td><span data-bind="text: $parent.pagedIndex($index() + 1)"></span></td>
                        <td><input class="form-control" data-bind="value: city" /> </td>
                        <td><input class="form-control" data-bind="value: zip" /> </td>
                    </tr>
                </tbody>
            </table>
            
            <ul class="pager">
                <li data-bind="css: {'disabled': !previousPageEnabled()}">
                    <a href="#" data-bind="click: previousPage">Previous</a>
                </li>
                <li data-bind="css: {'disabled': !nextPageEnabled()}">
                    <a href="#" data-bind="click: nextPage">Next</a>
                </li>
            </ul>
        </div>
        
        <div class="col-md-6">
            <table class="table table-condensed table-striped">
                <thead>
                    <tr>
                        <th><label class="control-label">Row</label></th>
                        <th><label class="control-label">City</label></th>
                        <th><label class="control-label">Zip</label></th>
                    </tr>
                </thead>
                <tbody...

JavaScript

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 PagedDataset = (function(data) {
    // Convert the data
    //var items = ko.observableArray(data); // Converts the data array to an ObserveableArray, but does nothing to the data properties.
    var items = ko.mapping.fromJS(data);    // Converts the data array to an ObserveableArray and the data properties to Observables

    // Setup the pager and related functions
    var currentPage = ko.observable(1),
        perPage = 10,
        pagedItems = ko.computed(function() {
            var page = currentPage(),
                start = perPage * (page - 1),
                end = start + perPage;
            return items().slice(start, end);
        }),
        pagedIndex = function(index) {
            return currentPage() == 1 ? index : (perPage *...