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

About these ads

About craigcav

Craig Cavalier works as a Software Developer for Liquid Frameworks in Houston Tx, developing field ticketing and job management solutions for industrial field service companies.

Posted on May 21, 2012, in Knockout and tagged , , , . Bookmark the permalink. 6 Comments.

  1. Hi Craig! I absolutely love your plugin. It makes KnockoutJS way more efficient!! I’m having a problem though, I hope you can help me with. How can I use your datasource plugin in combiniation with the mapping plugin? I would like to dynamically add some observables to the data retreived from the datasource, but this requires the mapping plugin.

  2. Hi Ronald – thanks for the feedback! I’m really glad you’re enjoying using this plugin. I don’t use the ko.mapping plugin myself, but I don’t see any reason the two wouldn’t work well together. I’ve created a small jsfiddle to demonstrate this here: http://jsfiddle.net/craigcav/zYW42/

    Hope this helps!

    • Hey Craig! Thanks for your input. It works like a charm and now I understand your plugin a lot better. I was trying to put the mapping inside the ko.observableArray([]).extend({ …. }); I now realise how stupid that was.

      Right now I’m looking into pushing new records into the mapped array, as well as converting your plugin so it can work as an infinite scrolling datasource! I’m not very succesful at it actually, but hey… I heard about the existence of KO just a few weeks ago.

  3. Hi,

    I am also new to Knowout.js, so maybe I am missing something very obvious: The resulting items array seems to be missing the ko.observableArray-methods like remove, right? I tried to call a self.items.remove(item) for a delete button, but got an error that the remove-method was not defined.
    Unfortunately the original fiddle is no longer working, probably due to a knockout.js-update in the meantime. I was wondering if this project is still being maintained or if you’d recommend using the mapping plugin.

    Kind regards

    Markus

    • Hi Markus, Thanks for using my plugin. I’ve updated the fiddle now to use the latest knockout version. You are correct the resulting items array `viewModel.items` is no longer an observableArray (it is a computed observable) and therefore no longer has the ko.observableArray methods such as remove.

      A simple work around to this is to set up your view model like this:


      var viewModel = {
      items: ko.observableArray([])
      };

      viewModel.extend({
      datasource: getAnimals
      });

      To be honest with you though Markus, I don’t personally get a lot of use out of this plugin any more (mainly as I’m doing a lot of offline work and don’t make ajax requests this way). I personally didn’t get a lot of mileage from the mapping plugin either, but your mileage may vary.

  4. Thanks – this is one way for this to work; as I needed the model to reload data from the datasource anyway, I chose a little detour via one ko.observable bound to a hidden input, so any update to the value of this hidden input field triggers a reload of the model.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: