Telerik blogs

In my previous post I walked through the basic steps to use a kendo.data.DataSource to build a very simple "to do" list. That version of the app was mostly read-only, with the exception of being able to mark an item as done. You couldn't, however, add or remove any items. It turns out adding and removing is as easy as anything else that has been done in this app, though, and it only takes a few lines of markup and JavaScript to add these features.

The Todo App

Here's what my app looks like with the add / remove features in place:

The complete code can be found in this JSFiddle

Once again, I want to note that this application is inspired by the TodoMVC app demos, but it is not intended to be a complete or canonical representation of TodoMVC or Kendo UI. I'm building a demo that is meant to exercise the Kendo DataSource object in a meaningful way, so that I can learn more about this framework peice and hoepfully help others better understand what it can do for us.

Additional Markup

There have been a few changes to the markup, as you can see from the output. I've added a <frameset> to the page to provide an area to add new "to do" items. I've also adjusted the table slightly and added a "remove" button below it, to allow us to remove items that are already done.

Todo Markup

    <fieldset class=".add-new">
        <legend>Add A Todo</legend>
        <p>Enter a to do item description</p>
        <input name="description" />
        <button id="add">Add</button>
    </fieldset>

    <!-- ... -->

    <tr>
        <td>Actions: </td>
            <td colspan="2">
                <button id="remove-done">Remove Done Items</button>
        </td>
    </tr>

This is simple markup with no Kendo templates at this point. The important points are the input ID's, which we will use in our JavaScript so that we can add new "to do" items or remove the ones that are complete.

Adding A New To Do Item

Handling the addition of a "to do" item is done with a jQuery selector and a DOM "click" event for the "add" button. When we receive that event, we read the "to do" description from the input box. Then we build an object literal that contains all of the fields we need: an id, a "done" status and the description we entered.

Adding A To Do Item

  // Handle adding a new to do item
  $("#add").click(function(e){
    e.preventDefault();

    var $todo = $("input[name='description']");

    currentId += 1;
    dataSource.add({
      id: currentId,
      done: false,
      description: $todo.val()
    });

    $todo.val("");
    $todo.focus();
  });

Adding a "to do" item to the DataSource is very simple, as you can see. You only need to call the add method on the dataSource instance, and pass in the object to be added.

To create the "to do" items with a unique ID, I've added a "currentId" variable to this code. This is used as a simple counter to give me an incremental ID to attach to new "to do" items, which is used to determine the item that is being marked as done (see the previous post for more information on that) when clicking a "done" checkbox. The currentId variable increments prior to every use, ensuring that we have a unique ID for each item we add.

I'm also clearing out the description input box after the item has been added and then re-focusing the browser that input box. This allows me to add a number of "to do" items faster as it reduces the number of clicks needed to create one and re-focus on the text input field.

Clearing The Done Items

Clearing the items that are already "done" takes a little more work than adding an item. There are no methods on the dataSource to bulk-remove items, at this time. That means I need to call the .remove method to remove the individual items that are "done".

Now, the question is: How do I get the list of items that are done?

The first idea that I had was to use the .filter method to filter down the list, the way I am doing for the filtering drop list. However, this method doesn't return anything to me. It just sets up some filters and triggers some events so that I can call the view method to get the list of items that match the filter. This sounded like a good idea at first, until I remembered that calling .filter(...) would trigger the "change" event and cause the item list to re-render. The net result of that is the view would re-render itself with the list of "done" items. This doesn't sound too bad off-hand, until I account for the different filter states for the view. If I'm looking at "all" items, I don't want to filter down to just the "done" items so I can delete them. I want to just get rid of them without re-rendering the list down to the items that will be removed first.

The solution I cam up with needs a little more code, then, because of the way the filter method works. I have to read the raw data from the dataSource, iterate over the array that this method returns, and check the "done" status of each item in the array. As I find items that are "done", I can then remove them from the dataSource.

Removing "Done" To Do Items

  // Handle removing cleared items
  $("#remove-done").click(function(e){
    e.preventDefault();

    var raw = dataSource.data();
    var length = raw.length;

    // iterate and remove "done" items
    var item, i;
    for(i=length-1; i>=0; i--){

      item = raw[i];
      if (item.done){
        dataSource.remove(item);
      }

    }

  });

The .data() method, unlike .view() will return the raw data from the underlying data store, unfiltered, unsorted, un-paged. It just hands the array of objects back to me in order to let me do what I want with them. This gives me the ability to look at all of the items in the dataSource instead of just the filtered, sorted and paged list that .view() would return.

Once I have that raw list, I set up a loop to iterate the length of the array. Within that for-loop, I grab the "to do" item for the index that I'm currently on and check to see if it is "done" or not. If it is, I tell the dataSource to remove that item.

Iterating Backwards

Did you notice that I'm looping through the list backwards?

Inverse For-Loop

  var i, length=data.length;

  for (i=length-1; i>= 0; i--){
    // ...
  }

Instead of the usual for-loop counting from 0 to a length, I am counting from length-1 back down to 0. I'm doing this because as I iterate through the array, I am telling the dataSource to remove the items. Since the dataSource handed me a reference to the array that it holds, this means I'm removing items from the array that I'm iterating. If we count from 0 to length, removing an item would throw off the iteration and cause errors. We would skip every other item and we would end up counting past the end of the array due to the length being modified as I go. But by counting backwards - from the end of the list back to the beginning - I can avoid these problems. Counting down to zero will always get to zero, no matter how many items I remove.

Editing Items And Other Features

I havent set up an edit feature for the "to do" items yet, other than allowing them to be marked as "done". It should be fairly simple to put this in place, but I'll leave that up to you as an exercise to complete. How would you set up the edit form vs add form: would you re-use the same form, or have a different form for that? What would you have to do with the dataSource and the items within in to facilitate editing?

While this little "to do" application is not terribly feature-rich or beautiful, it is giving us a good idea of what the DataSource can do for us. But there's much more that it can do, than what we have seen so far. For example, the DataSource can manage remote data sources for us - reaching out to a server somewhere, to read, write and update data as needed. I'll dig in to remote data in a future post, and show the basics of what it takes to read and write data to a server.


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.