Blogs

Learning kendo.data.DataSource

by | Comments 10

As a new member of the Kendo UI team, I have a lot to learn. There are more controls, options, framework bits and other features found in Kendo than I had ever dreamed of while using Kendo UI in my previous projects – and we are constantly adding more.

One of those features that I haven’t used prior to joining the team is the kendo.data.DataSource. The Getting Started docs bill this as “an abstraction for using local (arrays of JavaScript objects) or remote (XML, JSON, JSONP) data” which means it can do a lot to simplify data management in our JavaScript apps.

An Oversimplified “To Do” App

To begin my journey in to learning the DataSource, I built is an over-simplified “to do” list. (Note that this sample code is inspired by the TodoMVC samples, but is not intended to be a complete implementation of that project.)

You can view the full source and live version here: http://jsfiddle.net/derickbailey/9q4qv/

As you can see, this “app” is barely an app – it only does 3 very simple things:

  1. Display a list of hard-coded “to do” items
  2. Allow you to mark items as “done” or not
  3. Allow you to filter the displayed list of items based on the “done” status

but to learn about the DataSource and building an app with it, this was a good place to start.

Note that I could have used a full Kendo MVVM stack with data-bind attributes and the control suite that Kendo UI provides. But the point of this code was not to build the ultimate, perfect to-do list. I’m really only interested in the DataSource and what it can do for me. Any other code that I put in place should only play a supporting role in helping me exercise the DataSource capabilities.

The Code And Markup

There’s very little to this app. In the markup there’s a table layout to display the items and a template to render each item in to the table.

Todo Markup

  

<script id="template" type="text/x-kendo-template">
    <tr>
        <td> 
            <input type="checkbox" class="done" data-id="#= id #" #if(done){# checked="checked" #}#></input>
        </td>
        <td>
            #= id #
        </td>
        <td>
          #= description #
        </td>
    </tr>
  </script>

  <div id="example" class="k-content">
    <div class="demo-section">
      <table id="todos" class="metrotable">
        <thead>
          <tr>
            <th>Done</th>
            <th>ID</th>
            <th>Task</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td colspan="3"></td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <td></td>
            <td colspan="2">
              Show: 
              <select id="filter">
                <option selected="selected" value="all">All</option>
                <option value="done">Complete</option>
                <option value="not done">Incomplete</option>
              </select>
            </td>
          </tr>
        </tfoot>
      </table>
    </div>
  </div>

In the JavaScript code, there’s a hard-coded list of “to do” items, a data source instance to work with the items, a “change” event handler to render the list of items, code to handle an item being marked as “done” or not, and code to handle filtering the items.

Todo JavaScript

  // To Do Item Template
  // -------------------

  var template = kendo.template($("#template").html());

  // To Do Item Data
  // ---------------

  // A hard coded list of items to render
  var tasks = [
    { id: 1, done: false, description: "do stuff"},
    { id: 2, done: false, description: "more stuff"},
    { id: 3, done: true, description: "this stuff to do"},
    { id: 4, done: false, description: "that stuff we need"}
  ];

  // Build the data source for the items
  var dataSource = new kendo.data.DataSource({ data: tasks });

  // When the data source changes, render the items
  dataSource.bind("change", function(e) { 
    var html = kendo.render(template, this.view());
    $("#todos tbody").html(html);
  });

  // Initial read of the items in to the data source
  dataSource.read();

  // Handle And Respond To DOM Events
  // --------------------------------

  // Handling clicking the "done" checkboxes
  $("#todos").on("change", ".done", function(e){
    var $target = $(e.currentTarget);
    var id = $target.data("id");
    var item = dataSource.get(id);
    item.set("done", $target.prop("checked"));
  });

  // Handle the filters for showing the desired items
  $("#filter").change(function(e){
    var filterValue = $(e.currentTarget).val();

    var filter = {
      field: "done",
      operator: "eq"
    };

    if (filterValue === "done"){
      filter.value = true;
    } else if (filterValue === "not done"){
      filter.value = false;
    } else {
      filter = {};
    }

    dataSource.filter(filter);
  });

The Basic DataSource Setup

At a very high level overview, this isn’t much code or markup. The process that this app flows through is equally as simple, as well.

When the app starts (using the jQuery DOMReady event, which is handled by JSFiddle in this case), I build a Kendo Template (which I've talked about before, in the context of Backbone) to render each "to do" item. I then build a DataSource using an array of simple JavaScript objects as my items.

Todo DataSource

  // To Do Item Template
  // -------------------

  var template = kendo.template($("#template").html());

  // To Do Item Data
  // ---------------

  // A hard coded list of items to render
  var tasks = [
    { id: 1, done: false, description: "do stuff"},
    { id: 2, done: false, description: "more stuff"},
    { id: 3, done: true, description: "this stuff to do"},
    { id: 4, done: false, description: "that stuff we need"}
  ];

  // Build the data source for the items
  var dataSource = new kendo.data.DataSource({ data: tasks });

Next, I set up a "change" event handler on the dataSource instance, and then "read" the data in to the dataSource.

Todo DataSource Read / Change

  // When the data source changes, render the items
  dataSource.bind("change", function(e) { 
    var html = kendo.render(template, this.view());
    $("#todos tbody").html(html);
  });

  // Initial read of the items in to the data source
  dataSource.read();

Calling “read” on the dataSource instance will tell it to read the raw data that I provided. This will trigger the “change” event from the dataSource, which tells the app to render the current view of the data and stuff the result in to the <table> in the DOM.

This “change” event handler is very simple, but also very important. We’ll see this called again when we get to the checkbox handler and filtering functionality.

Handling The "Done" Checkboxes

Next, a DOM event handler is set up to listen for change events on the “done” checkboxes. When a checkbox is checked or unchecked, the id for the item is pulled from a “data-id” attribute in the DOM. This is used to load the correct item from the dataSource so that it can be updated appropriately.

Todo Item Done

  // Handling clicking the "done" checkboxes
  $("#todos").on("change", ".done", function(e){
    var $target = $(e.currentTarget);
    var id = $target.data("id");
    var item = dataSource.get(id);
    item.set("done", $target.prop("checked"));
  });

Changing the “done” value for a model that has been pulled from the dataSource will cause a “change” event on the dataSource itself. This will cause the change handler from above to fire, re-rendering the list of items in to the DOM.

A Brief Detour In To jQuery And DOM Events

It is important to note how the “change” event is set up for the checkboxes in this code. When I first put this in place, I was doing this:

Bad jQuery DOM Event

$(".done").change(function(e){
  // ...
});

But I found that this code only worked the first time the page rendered the list of items. After I filtered the list and it re-rendered (which I’ll cover in a moment), the check box change events no longer worked. This is because the jQuery selector used to find the checkboxes only runs once when the app starts. That means the checkboxes that were originally found on the screen would have the event handler set up, but re-rendering the list produces new checkboxes which means they will not have the event handler.

There are two basic solutions for this problem:

  1. Re-bind the checkbox event handler after every rendering of the list
  2. Use jQuery’s DOM event delegation

I chose option #2 for two reasons:

First, re-binding the checkbox handlers would cause the code to be a bit more ugly than I want. I don’t want to set up the checkbox click handlers inside of my dataSource’s “change” event handler because of the nested callbacks that this would cause. Nested callbacks are not always bad, but they quickly lead to code that is difficult to read, understand and maintain.

And second, if the list of items was large enough, it would cause performance problems to bind a change event to every checkbox. Instead, I want one change event that knows to listen to any checkbox.

Using jQuery’s DOM event delegation solves both of these problems as well as the original problem of the events not firing after re-rendering the list. To do this, I find the <table> element that will eventually contain the checkboxes, using a jQuery selector. Once I have that element, I attach a “change” event listener and use another selector to specify that this change event should fire when any checkbox in the table triggers a change event.

jQuery is smart enough to set up the event handler so that it will receive all change events from any checkbox in the table, no matter how many times I re-render the list of items and checkboxes for the them. For more information on this, see jQuery’s documentation for the “on” method.

Filtering The List Of Items

The last chunk of code in this little app is for filtering the list of items. You can choose from “All” to show all of the items, “Done” to only show the items that are done, and “Not Done” to only show the items that are not done. To do that, I set up a “change” event on the select box and handle configuring the DataSource filter in that event.

Todo Item Filter

  // Handle the filters for showing the desired items
  $("#filter").change(function(e){
    var filterValue = $(e.currentTarget).val();

    var filter = {
      field: "done",
      operator: "eq"
    };

    if (filterValue === "done"){
      filter.value = true;
    } else if (filterValue === "not done"){
      filter.value = false;
    } else {
      filter = {};
    }

    dataSource.filter(filter);
  });

In this event handler, I’m setting up a basic set of options for the filtering – telling it what field to filter on, and what operator to use for the filter. In this case, I’m checking to see if the “done” flag on my items are “eq” (equal) to … a value yet to be determined.

Once I have the basic configuration set up, I check the filter value from the select box. If the value is “done” or “not done”, I set the filter.value appropriately. If the filter is set to “all”, though, I wipe out the filter configuration entirely. This effectively means no filtering should be done.

After setting up the filtering configuration, I call the dataSource.filter method and pass the configuration to it. This call triggers a “change” event on the dataSource object, which re-renders the list of items on the screen. When the dataSource “change” fires in this case, the “ds.view()” method returns the list of items that match the current filters, paging, sorting and other criteria set up on the dataSource instance. This is what allows the rendered list to only show the filtered items.

As I noted above, checking or un-checking the “done” field for any of the items will trigger a “change” event on the dataSource. The combined effect of this with the dataSource.filter means that any time you are showing a filtered list of items (other than “All”), changing the “done” status of an item will cause that item to disappear from the screen. This happens because the item’s data has been updated and it no longer matches the current filter criteria. To see this item again, switch to the other filter or remove the filter by selecting “All”.

And So Much More ...

The DataSource does far more than I could learn in a day or cover in a single blog post. I’ve only touched on the surface of what it can do in this post, with a list of “to do” items that can’t be added to or have any removed. Be sure to watch this blog for more information, though. As I continue to learn more about this framework piece, I’ll continue to post on what I am doing with it, why I am doing it that way, and how I am getting it done.

About the Author
is a Developer Advocate for Kendo UI, a developer, speaker, trainer, screen-caster and much more. He's been slinging code since the late 80’s and doing it professionally since the mid 90's. These days, Derick spends his time primarily writing javascript with back-end languages of all types, including Ruby, NodeJS, .NET and more. Derick blogs at DerickBailey.LosTechies.com, produces screencasts at WatchMeCode.net, tweets as @derickbailey and provides support and assistance for JavaScript, BackboneJS, MarionetteJS and much more around the web.

10 Comments

  1. 1 Paul Yoder 25 Jan 2013
    With your background in Backbone.js, I would love to hear your thoughts on the differences between Backbone.Collection and kendo.data.DataSource. And when you would recommend using one over the other.
  2. 2 Derick Bailey 25 Jan 2013
    Hey Paul,

    The first thing that comes to mind is the number of data transports that DataSource supports: OData, JSON, and XML. This could be useful in systems that need to take advantage of legacy back-ends and web services. Beyond that, I'll have to continue digging in to the DataSource and learn more about how it works and why.

    I'm also wondering if there's a case where we don't say "Backbone.Collection vs. kendo.DataSource" but instead ask, "How can be leverage kendo.DataSource to populate our Backbone.Collection?"

    This should be fun. :)
  3. 3 Ranga 30 Jan 2013
    Hi,

    I am Ranga, I need one help for getting grid first item by using dataSource.at(0);

    It is not working properly.

    If any example is there is send it to me as rangaitapp@gmail.com
  4. 4 Derick Bailey 30 Jan 2013
    Hi Ranga,

    It may be better to post your question to http://stackoverflow.com and mark it with the KendoUI tag. That way you'll have enough space to provide detail about the problems you are having, what your trying to accomplish, and relevant code samples. We'll also be able to keep the discussion about your specific issue focused, instead of having it mixed with other blog comments here.

    Thanks.
  5. 5 robort 01 Feb 2013
    Hi Derick Bailey,
    I am robort...I have  kendo grid  in my application.And also have dropdown list with checkboxes.
     i found  the solution with single selection.http://jsfiddle.net/schapman/HyHZG/5/ .But i want change the grid values with multiple selection.
    http://jsfiddle.net/MG89G/253/#run how to do? please help me

  6. 6 Manik Mittal 13 Feb 2013
    Hi Derick,

    I am trying to parse the datasource in JQuery. In my datasource, I have two rows one with the dynamic datacolumn header, another with the actual data. The way it is set up is to get both the rows from the JSON action method and then filter the datasource to get the headers and data. Following is the code I am using:

    var baseLineDataSource = new kendo.data.DataSource({
                            transport: 
                            {
                                read: 
                                {
                                    url: "@Url.Action("GetDWConversionBaselineData", "Home")",
                                    dataType: "json",
                                    data: function (){
                                        return { "DWConversionID": id };
                                    }
                                }
                            }
                        });
            baseLineDataSource.read();
            baseLineDataSource.fetch();
            baseLineDataSource.get();
    After this call, if I am trying to put break point and check
    baseLineDataSource.data().length, it is coming out 0, though I have assigned this data as to the Grid after this, the grid is getting loaded with data. Am I missing something?
  7. 7 Derick Bailey 13 Feb 2013
    Hi Manik,

    The call to load the data from the web server is going to be asynchronous. Running code to check the data length immediately after this code will almost always show 0 because the data will not have returned from the server yet.

    To fix this, you'll need to wait for the data to be loaded before checking the length. The easiest way to do that is to listen for the "change" event on the DataSource instance, as I've shown in this blog post. Once you have the change event fired, you should be able to get the length of the data.

    I hope that helps!
  8. 8 Ranga Reddy 21 Feb 2013
    Hi,

    While loading Js file dataSource in not calling immediately.
    While giving setTimeout() { },100); then only dataSource data is reading. 

    How to call a dataSource while loading js file itself i want to get the Data. How?
  9. 9 Derick Bailey 21 Feb 2013
    Hi Ranga,

    I'm not sure I understand the question entirely. Generally speaking, the DataSource loads data asynchronously, which means you can't run code that needs the data until the data has been read by the DataSource. The best way to know when the data has been read is to use the "change" event that I show above.

    It may be easier to answer the question if you could provide some more code samples and a larger description of the problem. The best place to do this would be at http://stackoverflow.com, tagging your question with the "KendoUI" tag.
  10. 10 Cecilia 19 Jun 2013
    Hi!
    I have a kendo Grid, I need to loop through all data (grid has pages), the problem is that data() gets the items of the current page shown. How can I resolve it?
    var sNews = "";
    var sDataSource = $('#News').data('kendoGrid').dataSource;
                 var sQty = Math.ceil(sDataSource._total / sDataSource._pageSize);
                 var sData;

                 for (var i = 1; i <= sQty; i++) {
                     sDataSource.page(i);
                     sData = sDataSource.data(); //aca esta el problema
                     for (var j = 0; j < sData.length; j++) {
                         sNews =  sNews + '{ "new_Number": "' + sData[j].new_Number + '"},';
                     };
                 };
    Thanks in advance,
    Cecilia

Comment

  1.