Telerik blogs

In my previous post I showed you how to toggle the checkbox of a Kendo UI TreeView item when clicking on the label. Now that you have those items checked, it’s time to get the list of models from the underlying DataSource so you post them back to a server, manipulate them in JavaScript, or whatever else you may need to do.

Which Items Are Checked?

Determining which items are checked is fairly straightforward. Grab the current .view() from the TreeView’s .dataSource, then iterate over that array. For each item, check to see which ones have a .checked attribute set to true.

function getCheckedItems(treeview){
  var node;
  var checkedNodes = [];
  var nodes = treeview.dataSource.view();

  for (var i = 0; i < nodes.length; i++) {
    node = nodes[i];
    if (node.checked) {
      checkedNodes.push(node);
    }
  }

  return checkedNodes;
}

The result of the getCheckedItems function is an array of models from the TreeView’s DataSource. You can iterate this array, turn them in to JSON, send them to the server or do whatever you need with them.

Handling Children (Recursion)

Getting the list of items at the top level of a treeview is easy, as shown above. You just need need to look through the DataSource and find the checked items. But trees are not made for a single level list. They are made to be hierarchical - to contain children and children of children, on down to the depths of however far the data structure goes. To get the checked items way down deep in the tree, then, you’ll need to add a bit of recursion to the getCheckedItems method.

The first thing to do is check if the current node in the for loop has any .children. If it does, then you’ll need to travel down in to the children and look for the checked ones.

  for (var i = 0; i < nodes.length; i++) {
    node = nodes[i];
    if (node.checked) {
      checkedNodes.push(node);
    }

    // to understand recursion, first
    // you must understand recursion
    if (node.children){
      getCheckedItems(... ??? );
    }
  }

Doing the recursion is simply making a call back to getCheckedItems - but now the treeview parameter for this method becomes a problem. You don’t need to send in the TreeView instance anymore, because you’ve already retrieved the information that you need from the TreeView. So what do you pass here? You could try to just add a second parameter to pass in the information you need and then ignore the treeview parameter, but this gets ugly quickly and it makes the method signature confusing. When do you need to / not need to pass in the treeview?

Instead, a better option would be to have two methods involved in the process: 1) to start the process, where you pass in the TreeView instance, and 2) to process the nodes and recurse in to itself when it finds child nodes.

function getCheckedItems(treeview){
  var nodes = treeview.dataSource.view();
  return getCheckedNodes(nodes);
}

function getCheckedNodes(nodes){
  var node;
  var checkedNodes = [];

  for (var i = 0; i < nodes.length; i++) {
    node = nodes[i];
    if (node.checked) {
      checkedNodes.push(node);
    }
  }

  return checkedNodes;
}

With these methods separated, the getCheckedNodes method can be modified for recursion:

function getCheckedItems(treeview){
  var nodes = treeview.dataSource.view();
  return getCheckedNodes(nodes);
}

function getCheckedNodes(nodes){
  var node, childCheckedNodes;
  var checkedNodes = [];

  for (var i = 0; i < nodes.length; i++) {
    node = nodes[i];
    if (node.checked) {
      checkedNodes.push(node);
    }
      
    // to understand recursion, first
    // you must understand recursion
    if (node.hasChildren) {
      childCheckedNodes = getCheckedNodes(node.children.view());
      if (childCheckedNodes.length > 0){
        checkedNodes = checkedNodes.concat(childCheckedNodes);
      }
    }

  }

  return checkedNodes;
}

There are a few things to notice, here.

First off, the inner call to getCheckedNodes uses the node.children.view() method. The children attribute is a DataSource itself, and it contains the child models for the current node. Calling .view() on this is the same as calling the dataSource.View() on the TreeView, as was done in the first method.

Secondly, the resuling list of child nodes is then merged in to the parent list of checked nodes using the Array concat method. This produces a flat list of models, rather than a hierarchical list of models.

Lastly, the Array concat method does not modify the array from which you called the method. Instead, it returns a new array that has the resulting combination of items in it. That’s why the checkedNodes variable is assigned to the result of the checkedNodes.concat call.

As An Add-on For The TreeView

All this is great - but we can do one better, still! Why have this has a separate method just hanging out in your JavaScript file when you could add this as a method on the TreeView itself?

It’s easy - you only need to attach the method to the prototype of the TreeView, and then change the method signature to not pass in the treeview parameter. Once you’re attached to the TreeView, you’ll have this referencing the TreeView instance, so there isn’t any need to pass the TreeView in as a parameter.

kendo.ui.TreeView.prototype.getCheckedItems = (function(){

  function getCheckedItems(){
    var nodes = this.dataSource.view();
    return getCheckedNodes(nodes);
  }

  function getCheckedNodes(nodes){
    var node, childCheckedNodes;
    var checkedNodes = [];

    for (var i = 0; i < nodes.length; i++) {
      node = nodes[i];
      if (node.checked) {
        checkedNodes.push(node);
      }
        
      // to understand recursion, first
      // you must understand recursion
      if (node.hasChildren) {
        childCheckedNodes = getCheckedNodes(node.children.view());
        if (childCheckedNodes.length > 0){
          checkedNodes = checkedNodes.concat(childCheckedNodes);
        }
      }

    }

    return checkedNodes;
  }

  return getCheckedItems;
})();

Also note that I’ve wrapped the two function inside of an Immediately-Invoking Function Expression (IIFE). In this case, the IIFE is used as a JavaScript Module Pattern. This provides some safety for our code, ensuring we don’t accidentally override any global methods or leak any global methods of our own. This means you can add as many methods and variables and other bits to the implementation as you need, and not worry about any other code accidentally clobbering it. The return statement at the bottom of the IIFE sends the getCheckedItems method out to the assigned attribute on the TreeView prototype, making the method available on any TreeView instance. With that in place, you can call treeView.getCheckedItems() on any TreeView and get an array of checked items returned to you.

See It In Action

This JSFiddle shows the result of adding the getChecked method to your app, and calling it when a button is clicked.


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.