LIDOR SYSTEMS

Advanced User Interface Controls and Components

Tri-State Check Boxes in jQuery TreeView

Created: 15 September 2014

Normally each item in jQuery TreeView is shown using a label and an icon. We can modify the item appearances using templates which may contain custom HTML elements arranged in custom layouts. For the purpose of this article we will show you how to add a check box to each item and placed in before its label. Also we will show you how to update the value of parent and child check boxes when a specific item check box is clicked.

TreeView is part of IntegralUI Studio for Web
a suite of UI Components for development of web apps

At first we need to add a TreeView element to our page and create an instance of it using jQuery:

$(document).ready(function(){

// Create an instance of TreeView widget

var $tree = $('#treeview').treeview({

itemSpacing: 3

});

});

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

<link rel="stylesheet" href="css/integralui.treeview.css" />

<link rel="stylesheet" href="css/themes/theme-blue.css" />

<script type="text/javascript" src="external/jquery-1.9.1.min.js"></script>

<script type="text/javascript" src="external/jquery.ui.core.min.js"></script>

<script type="text/javascript" src="external/jquery.ui.widget.min.js"></script>

<script type="text/javascript" src="js/jquery.integralui.widget.min.js"></script>

<script type="text/javascript" src="js/jquery.integralui.treeview.min.js"></script>

</head>

<body>

<div id="treeview" class="widget"></div>

</body>

</html>

<style type="text/css">

.widget

{

width: 300px;

height: 300px;

}

.item-content span

{

display: inline-block;

}

input[type="checkbox"]

{

margin: 0 2px 0 0;

padding: 0;

vertical-align: middle;

}

</style>

Next we will create a simple tree hierarchy containing few items to simplify our example. Each item will contain a check box shown to the left of its text. For this we are using a simple template:

// A template which adds a checkbox and text to each item

var generateItemContent = function(text, checked){

var content = "<div class='item-content' data-element='content'>" +

"<input data-element='checkbox' type='checkbox' />" +

"<span>" + text + "</span>" +

"</div>";

 

return content;

}

 

// A sample tree structure from which TreeView will be populated

var treeStructure = [

{

id: 1,

content: generateItemContent("item1"),

items: [

{ id: 11, pid: 1, content: generateItemContent("item11"), checkState: 'checked' },

{ id: 12, pid: 1, content: generateItemContent("item12") }

]

},

{

id: 2,

content: generateItemContent("item2"),

items: [

{ id: 21, pid: 2, content: generateItemContent("item21") },

{

id: 22,

pid: 2,

content: generateItemContent("item22"),

items: [

{ id: 221, pid: 22, content: generateItemContent("item221"), checkState: 'checked' }

]

},

{ id: 23, pid: 2, content: generateItemContent("item23") }

]

},

{ id: 3, content: generateItemContent("item3"), checkState: 'checked' }

]

As you may notice from above tree structure, we are using the content property to add a custom HTML generated content to each item. This is required if we want to create items with custom elements arranged in different layouts than default one.

Now when we have our sample data, we can populate the TreeView with it. For this purpose we will be using the following function:

// Populate the TreeView with data from our tree structure

var populateTree = function(){

$tree.treeview("clearItems");

for (var i = 0; i < treeStructure.length; i++)

$tree.treeview("addItem", treeStructure[i]);

}

Whenever this function is called, at first will clear the current content of TreeView prior adding any new items. We are using the clearItems method to empty the TreeView and addItem method to add new items to it.

When we refresh our web page at this stage, the TreeView will be shown with all items containing a check box and a label. However, when check boxes are clicked, the values of other check boxes above and below will remain the same, that is unchecked.

We need to use jQuery to change the check box value to one of three states: checked, indeterminate and unchecked. The indeterminate value cannot be set by changing the checked attribute value of check box element. It can only be set by using the jQuery prop method:

switch (item.checkState){

case 'checked':

itemCheckElem.prop("checked", true);

itemCheckElem.prop("indeterminate", false);

break;

 

case 'indeterminate':

itemCheckElem.prop("indeterminate", true);

break;

 

default:

itemCheckElem.prop("checked", false);

itemCheckElem.prop("indeterminate", false);

break;

}

As it can be seen from above code, to make a check box with indeterminate value, we need to use the inderminate property of checkbox element. However, when this property is set to true, it may cause problems when checkbox is checked or unchecked. To make sure this works in those cases, we need to set the indeterminate property value to false.

Whenever a checkbox value is changed, we need to update the values of parent and child check boxes. To update the parent check boxes, we need to create a loop which determines all parent items for our clicked item:

// Update the checkbox of parent items

var updateParentItem = function(item){

var parent = $tree.treeview("getItemParent", item);

 

while (parent){

if (parent.items){

var checkCount = 0;

for (var i = 0; i < parent.items.length; i++){

if (parent.items[i].checkState === 'checked')

checkCount++;

}

 

if (checkCount === parent.items.length)

parent.checkState = 'checked';

else if (checkCount > 0)

parent.checkState = 'indeterminate';

else

parent.checkState = 'unchecked';

 

updateCheckBox(parent);

}

 

parent = $tree.treeview("getItemParent", parent);

}

}

To locate the parent item we are using the getItemParent method. This method returns the found parent of specified item or if there is no parent a null value. By cycling through all parent items we are checking how many items have their check box checked. If all items have their check boxes checked, that means that the parent item should also have its checkbox value set to checked. If some items have check boxes with indeterminate value, the parent check box will also have the indeterminate value. Finally if all child items have unchecked checkboxes, the parent check box will also become unchecked.

In similar way we can cycle to all child items for our current item, and change the values of all child check boxes. We need to do this, because when parent check box changes its value to checked or unchecked, this change must also reflect all child check boxes to have their value set to checked or unchecked respectively. Here is the code:

// Update the checkbox of child items

var updateChildItem = function(parent){

if (parent && parent.items){

for (var i = 0; i < parent.items.length; i++){

if (parent.checkState === 'checked')

parent.items[i].checkState = 'checked';

else

parent.items[i].checkState = 'unchecked';

 

updateCheckBox(parent.items[i]);

updateChildItem(parent.items[i]);

}

}

}

To update the value of check box elements we are using the following function:

// Update the value of checkbox HTML element

var updateCheckBox = function(item){

var itemElem = $tree.treeview("getItemElement", item);

if (itemElem){

var itemCheckElem = itemElem.find("[data-element='checkbox']").eq(0);

 

switch (item.checkState){

case 'checked':

itemCheckElem.prop("checked", true);

itemCheckElem.prop("indeterminate", false);

break;

 

case 'indeterminate':

itemCheckElem.prop("indeterminate", true);

break;

 

default:

itemCheckElem.prop("checked", false);

itemCheckElem.prop("indeterminate", false);

break;

}

}

}

Because each checkbox element contains a custom HTML5 attribute named data-element with value set to checkbox, we can easily locate it using the jQuery attribute selector. To make sure that only the first found checkbox element is selected, we are using the eq method.

When we have a reference to the element in the DOM, we can change its value using the jQuery prop method, like it is shown in above code.

The code so far was for changing the value of check box elements depending on checkState of corresponding items. We also need to allow changes to take place whenever a check box is clicked. To do this , we need to handle the change event for all check box elements:

// Rebind the change event for each checkbox

var refreshCheckBoxes = function(){

// Update the check box value to match the checkState value of their corresponding item

for (var i = 0; i < treeList.length; i++){

updateCheckBox(treeList[i]);

updateParentItem(treeList[i]);

}

 

// Handle the change event for all check boxes

$tree.find("[data-element='checkbox']").each(function(index){

var $checkBox = $(this);

 

$checkBox

.off("change click")

.on({

change: function(){

var currentValue = $(this).prop('checked');

 

if (index >= 0 && index < treeList.length){

var item = treeList[index];

if (item){

item.checkState = currentValue ? 'checked' : 'unchecked';

updateChildItem(item);

updateParentItem(item);

}

}

},

click: function(e){

e.stopPropagation();

}

});

});

}

Finally when tree item is expanded or collapsed all child elements are removed from the DOM. This will also remove the change event from any check box in child items. To make sure our code works in all cases, we will handle the aftercollapse and afterexpand events and rebind the change event and update the check boxes with value equal to the checkState of the item to which they belong.

// Handle the expand and collapse events so that check boxes are refreshed

$tree.on({

"aftercollapse": function(e){

refreshCheckBoxes();

},

"afterexpand": function(e){

refreshCheckBoxes();

},

"itemclick": function(e){

// Get the item that is clicked and update the value of all affected check boxes

if (e.object){

if (e.object.checkState === 'checked')

e.object.checkState = 'unchecked';

else

e.object.checkState = 'checked';

 

// Update the value of item checkbox element

updateCheckBox(e.object);

 

// Update the value of all child items

updateChildItem(e.object);

 

// Update the value of all parent items

updateParentItem(e.object);

}

}

});

As it can be seen from above code, we are also handling the itemclick event, to allow changes to the check boxes whenever item label is clicked.

A live demonstration above shows how all code in this article works. Whenever a check box is clicked, it triggers changes to all affected parent and child check boxes.

Did you Like this Article?


Enter your e-mail address below and you will receive latest articles as well as news on upcoming events and special offers.