Progressive Enhancement KnockoutJS Paging

by Daniel Lamb

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<b>Search Results</b>
<p>Progressive enhancement with KnockoutJS Paging</p>

<div class="pages">
    <a href="/page/1">1</a>
    <a href="/page/2">2</a>
    <a href="/page/3">3</a>
    <a href="/page/4">4</a>
    <a href="/page/5">5</a>
    <a href="/page/6">6</a>
    <a href="/page/7">7</a>
    <a href="/page/8">8</a>
    <a href="/page/9">9</a>
    <a href="/page/10">10</a>
</div>
<!-- The first page is rendered server side -->
<div id="list" data-bind="foreach: list">
    <div data-bind="PE, text: name">Listing 0</div>
    <div data-bind="PE, text: name">Listing 1</div>
    <div data-bind="PE, text: name">Listing 2</div>
    <div data-bind="PE, text: name">Listing 3</div>
    <div data-bind="PE, text: name">Listing 4</div>
    <div data-bind="PE, text: name">Listing 5</div>
    <div data-bind="PE, text: name">Listing 6</div>
    <div data-bind="PE, text: name">Listing 7</div>
    <div data-bind="PE, text: name">Listing 8</div>
    <div data-bind="PE, text: name">Listing 9</div>
</div>

CSS

#list div {
    background-color: #eee;
    margin: 5px;
    height: 100px;
}

JavaScript

//Custom binding to load the values into the view model
ko.bindingHandlers.PE = {
    init: function(element, valueAccessor, allBindings) {
        var bindings = allBindings();
        if (typeof bindings.text === 'function') {
            bindings.text($(element).text());
        }
    }
};

// listing model
var Listing = function (listing) {
    this.name = ko.observable('');
    // set initial values if provided
    if (listing) {
        this.name(listing.name);
    }
};

var getInitialData = function(results) {
    // create a list of empty listings
    var children = $('#list').children(),
        // create a list of listings
        list = _.range(children.length)
                .map(function (i) {
                    var elm = $(children[i]),
                        listing = new Listing();                                        
                    // get listing values from the DOM
                    ko.applyBindings(listing, elm[0]);
                    // convert the model to JSON
                    listing = ko.toJS(listing);    
                    // update the max id used so far
                    results.maxId += 1;
                    // remove all but the first element
                    if(elm.index() > 0)
                    {
                        elm.remove();
                    }                    
                    return listing;
                });    
    
    return list;
}

// search results view
var ResultsView = function () {
    var self = this;
    self.maxId = 0;
    self.list = ko.observableArray(getInitialData(self));
    self.pendingRequest = ko.observable(false)
};

var results = new ResultsView();

ko.applyBindings(results);

// listen for page clicks
$('.pages').on('click', 'a', function (e) {
    var link = e.target,
        pg = parseInt(link.href.substr(link.href.indexOf('/page/')+6));
        
    results.maxId = (pg * 10) - 10;
        
    getNextItems(10);
    
    return false;
});


function...