Telerik blogs

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

Derick Bailey

About the Author
Derick Bailey 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 atDerickBailey.LosTechies.com, produces screencasts atWatchMeCode.net, tweets as @derickbailey and provides support and assistance for JavaScript, BackboneJS,MarionetteJS and much more around the web.

Comments

Comments are disabled in preview mode.