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.
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.
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.
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.
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 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,