LIDOR SYSTEMS

Advanced User Interface Controls and Components

AngularJS Tree Grid with Cascading Checkboxes

Created: 10 July 2015

Updated: 02 June 2016

AngularJS Tree Grid comes with option to add a check box to its column cells. By default these check boxes are not related, and whenever their value changes, the parent and child rows which contains a check box will not update its value. We will show you how to create this functionality, which will auto-update values of cascading checkboxes for all rows.

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

At first, we need to create a grid structure that displays a check box in first column for each row. In order for cascading checkbox changes to work, we need to set checkboxes to accept tri-state values. In this mode, when check box is clicked, it will change its value to:

  • 'checked' - the check box is unchecked
  • 'indeterminate' - the check box in undetermined
  • 'unchecked' - the check box is unchecked

Demonstration above shows check boxes in the first column of Tree Grid. Initially, some of these check boxes are 'checked', and their parent rows update their check box value to 'indeterminate'.

Whenever a check box is clicked, the cellClick event is fired. By handling this event, we can add operations that will update the values of check boxes in parent and child cells. To handle this event is simple; we need to create a function in our app code and add its signature to the cell-click attribute, like this:

angular

.module("appModule", ["integralui"])

.controller("appCtrl", ["$scope", "IntegralUITreeGridService", "$timeout", function($scope, $gridService, $timeout){

$scope.onCellClick = function(e){

if (e.cell && e.cell.cid == 1){

var checkValue = e.cell.value;

switch (checkValue){

case 'unchecked':

// Check box can become set to 'indeterminate' state only when some child rows are unchecked while others are checked.

checkValue = 'checked';

break;

 

case 'indeterminate':

checkValue = 'checked';

break;

 

case 'checked':

checkValue = 'unchecked';

break;

}

 

updateCheckValues(e.row, checkValue);

}

}

}]);

<!DOCTYPE html>

<html>

<head>

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

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

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

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

<script type="text/javascript" src="external/angular.min.js"></script>

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

<script type="text/javascript" src="js/angular.integralui.lists.min.js"></script>

<script type="text/javascript" src="js/angular.integralui.checkbox.min.js"></script>

<script type="text/javascript" src="js/angular.integralui.treegrid.min.js"></script>

</head>

<body>

<div ng-app="appModule" ng-controller="appCtrl">

<iui-treegrid name="{{gridName}}" columns="columns" rows="rows" control-style="controlStyle" show-footer="false" cell-click="onCellClick(e)"></iui-treegrid>

</div>

</body>

</html>

.iui-treegrid-column-header-cell, .iui-treegrid-column-footer-cell

{

padding: 2px 2px;

}

.iui-treegrid-row-cell-content

{

padding: 2px 2px;

}

.row-hovered

{

background-color: #efefef;

}

.row-cell-hovered

{

color: #000080;

}

.row-selected

{

background-color: #dedede;

color: #008000;

}

.checkbox

{

width: 16px;

margin: auto;

}

.checkbox-box

{

border: 0;

display: inline-block;

width: 16px;

height: 16px;

}

.checkbox-checked

{

background-image: url("../../../resources/checkbox/checkbox-checked-7.png");

}

.checkbox-indeterminate

{

background-image: url("../../../resources/checkbox/checkbox-indeterminate-7.png");

}

.checkbox-unchecked

{

background-image: url("../../../resources/checkbox/checkbox-unchecked-7.png");

}

Because the cellClick event is fired for any clicked cell, we need to make sure that our operation is executed only for cells that belong to the first column. We can determine this by checking the cell cid field value. If it matches the value of our column with check boxes, the operation will be executed.

var getCheckCell = function(row){

var cell = null;

 

if (row && row.cells){

for (var j = 0; j < row.cells.length; j++){

if (row.cells[j].cid == 1){

cell = row.cells[j];

break;

}

 

}

}

 

return cell;

}

The cellClick event is fired before cell value is changed. Because of this, although the check box state is changed, the cell still has its old value. To handle this, we can add code which determines the correct state of cell value.

Now that we have this set up, we can create functions that will update the check boxes for cells in child and parent rows. For child rows, we will create a recursive functions that will execute for all child rows and update their checkbox value:

// Update the checkbox of child rows

var updateChildRowCheckValue = function(row, parentValue){

var cell = getCheckCell(row);

 

if (cell){

// Current Row

cell.value = parentValue;

 

// Child rows

var list = row.rows;

if (list && list.length > 0){

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

updateChildRowCheckValue(list[i], cell.value);

}

}

}

}

In similar way, we will create functions that will change the value of checkboxes in all parent rows for specified row. Depending on how many checkboxes in child rows are checked, undetermined or unchecked, the corresponding parent check box will changes its value accordingly.

// Update the checkbox of parent rows

var updateParentRowCheckValue = function(parent){

while (parent){

var parentCell = getCheckCell(parent);

if (parentCell){

var list = parent.rows;

if (list){

var checkCount = 0;

var indeterminateCount = 0;

 

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

var cell = getCheckCell(list[i]);

 

if (cell){

if (cell.value == 'checked')

checkCount++;

else if (cell.value == 'indeterminate')

indeterminateCount++;

}

}

 

if (checkCount == list.length)

parentCell.value = 'checked';

else if (checkCount > 0 || indeterminateCount > 0)

parentCell.value = 'indeterminate';

else

parentCell.value = 'unchecked';

}

}

 

parent = $gridService.getRowParent($scope.gridName, parent);

}

}

Finally, we can add the last function to the cellClick event, which will update checkbox cells in all parent and child rows:

var updateCheckValues = function(row, value){

// Child rows

updateChildRowCheckValue(row, value);

 

// Parent rows

var parent = $gridService.getRowParent($scope.gridName, row);

if (parent)

updateParentRowCheckValue(parent);

}

As it can be seen from above demo, whenever a check box is clicked, related parent and child checkboxes also update their value.

Newsletter


Sign-up to our newsletter and you will receive news on upcoming events, latest articles, samples and special offers.
Name: Email: *
*By checking this box, I agree to receive a newsletter from Lidor Systems in accordance with the Privacy Policy. I understand that I can unsubscribe from these communications at any time by clicking on the unsubscribe link in all emails.