Blog Archives

ko.datasource–Enable simple binding to remote data sources

When using Knockout, we often want to bind to data that is retrieved from an AJAX request. In addition, it is not always efficient to populate the entire view model when the page loads. When adding paging, sorting and filtering of our data into the mix, if we’re not careful we can end up with a lot of accidental complexity and code duplication on our hands. To avoid that happening, I’ve created a small plugin for Knockout that makes interacting with remote datasources simple. Lets take a look.

The Bindings

I want my knockout bindings to be as simple as possible. Regardless of where I get my data, my view should be written as if it were completely unaware of where the data came from. In that vain, our markup should be the same as if we were binding to a standard knockout observable or observable array.

Let’s assume we want to display some data in a table. Our markup might look like this:

<table>
    <
thead
>
        <
tr
>
            <
th>Id</th
>
            <
th>Name</th
>
            <
th>Sales</th
>
            <
th>Price</th
>
        </
tr
>
    </
thead
>
    <
tbody data-bind="foreach: items"
>
        <
tr
>
            <
td data-bind="text: id"></td
>
            <
td data-bind="text: name"></td
>
            <
td data-bind="text: sales"></td
>
            <
td data-bind="text: price"></td
>
        </
tr
>
    </
tbody
>           
</
table>

Nothing too complicated, or unexpected. Just the basic foreach and text bindings.

Of course since we might expect a fair few results, we wish to paginate our data. Let’s add some simple bindings for this too.

<span id="pager">
    <
button data-bind="click: items.pager.first">First</button
>
    <
button data-bind="click: items.pager.previous">Prev</button
>
    <
span class="summary"
>Page 
        <
span data-bind="text: items.pager.page"></span
> of 
        <
span data-bind="text: items.pager.totalPages"></span></span
>
    <
button data-bind="click: items.pager.next">Next</button
>
    <
button data-bind="click: items.pager.last">Last</button
>
</
span>

Again, nothing too drastic – some simple buttons for pagination and an indication of what page we’re on. Do note however that we’re binding to a pager attached to the items. More on that in a moment.

The view model

The model is where the magic happens, it’s where any rich interactions will be specified. To keep our models DRY however, we probably don’t want to specify things like pagination in every view model we create. With the ko.datasource plugin, we can keep things simple:

var viewModel = {
   
items: ko.observableArray([]).extend
({
        //getAnimals is a data service to populate the viewmodel

       
datasource: getAnimals
,
       
pager
: {
           
limit: 3
        }
    })
};

We have an observableArray of items, just as if we were working with data in-memory, but two extenders have been applied to the array -  a datasource and a pager.

The datasource extender takes a single parameter; a function that will call into our remote data source. This function could, for example, use the ajax librarys of jQuery to call a webservice. We’ll take a deeper look at this in a second.

The pager extender takes a single parameter also; an object indicating how many items we would like to see per page. It will also attach itself to our observable array to expose additional pagination properties and methods. This is what the pager in our view is bound to.

The remote call

As previously mentioned, we can use our favorite library to call into whatever remote datasource we want. Let’s say we’re using jQuery’s ajax API:

function getAnimals() {
   
$.ajax
({
       
type: ‘GET’
,
       
url: ‘/my/remote/endpoint/’
,
       
data
: {
           
limit: this.pager.limit
(),
           
startIndex: this.pager.limit() * ( this.pager.page()1
)
        },
       
context: this
,
       
success: function( data
) {
           
this( data.rows
);
           
this.pager.totalCount( data.count )
;
        },
       
dataType: ‘json’
    });
};

The datasource (the extended observable array) is set as the context of the this keyword and that means we have access to the pager options. When data is successfully retrieved from our AJAX call, we can replace the data in our datasource by writing to the observable:

var observableArray = this;
observableArray(data.rows);
//or just
this(data.rows);

Additionally, since we’re using a pager, we should tell the pager how many results the server has in total so that it can figure out how many pages there are:

this.pager.totalCount( data.count );

An important thing to note is that the function we use here will behave like a computed observable, that is, it will run automatically whenever any of its dependencies change. This means that as our pager changes page, or when the row limit (records per page) changes, the remote call will be reevaluated in order to fetch the new data to show. This also means that if we pass dependencies from our viewModel as parameters to the remote call, they will also cause the datasource to be updated if they change. This is very handy as we can also use this feature for additional server-side filtering if needed.

That’s it!

Seriously, that’s all you need. Our datasource will evaluate when the view is first bound to it, and then will be reevaluated whenever our remote calls’ dependencies change.

Check out a live example here:  http://jsfiddle.net/craigcav/UzUBm/

And download the source here: https://github.com/CraigCav/ko.datasource

Enjoy.

Acknowledgements

None of this would’ve been possible without the inspiring work Ryan Niemeyer put into documenting KnockoutJS on his blog. In particular without the following two posts, this plugin probably wouldn’t exist.

http://www.knockmeout.net/2011/04/pausing-notifications-in-knockoutjs.html

http://www.knockmeout.net/2011/06/lazy-loading-observable-in-knockoutjs.html

Advertisements