ASP.NET TreeView and Checkboxes

Download the project files.

Although the TreeView control is a very powerful one, there are some pitfalls that you can run into when using it. One of the most popular problem is using checkboxes in TreeView. Despite the fact that you can enable checkboxes by setting ShowCheckBoxes="All" you still have to write a lot of JavaScript code to make it work properly. The problems are:

My solution written with JavaScript and with the help of ASP.NET AJAX library (so, don't forget to put a ScriptManager to your page) solves both problems. You can view the demo here. It was tested in IE, Firefox, Safari and Opera.

There are some other solutions, but they are missing some features, for instance they don't convert a link click to a checkbox selection.

Providing you have a TreeView on your page, you have similar code:

<asp:TreeView ID="someTree" runat="server" ShowCheckBoxes="All">
    <Nodes>
        <asp:TreeNode Text="Root"> 
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Branch">
                <asp:TreeNode Text="Leaf" />
                <asp:TreeNode Text="Leaf" />
            </asp:TreeNode>
        </asp:TreeNode>
        <asp:TreeNode Text="Root">
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
        </asp:TreeNode>
    </Nodes>
</asp:TreeView>

You have to put the following code either to the <head> section or to a separate JavaScript file.

<script type="text/javascript">
    var TREEVIEW_ID ="someTree"; //the ID of the TreeView control
    //the constants used by GetNodeIndex()
    var LINK = 0;
    var CHECKBOX = 1;
    
    //this function is executed whenever user clicks on the node text
    function ToggleCheckBox(senderId)
    {
        var nodeIndex = GetNodeIndex(senderId, LINK);
        var checkBoxId = TREEVIEW_ID + "n" + nodeIndex + "CheckBox";
        var checkBox = document.getElementById(checkBoxId);
        checkBox.checked = !checkBox.checked;
        
        ToggleChildCheckBoxes(checkBox);
        ToggleParentCheckBox(checkBox);
    }
    
    //checkbox click event handler
    function checkBox_Click(eventElement)
    {
        ToggleChildCheckBoxes(eventElement.target);
        ToggleParentCheckBox(eventElement.target);
    }
    
    //returns the index of the clicked link or the checkbox
    function GetNodeIndex(elementId, elementType)
    {
         var nodeIndex;
         if(elementType == LINK)
         {
            nodeIndex = elementId.substring((TREEVIEW_ID + "t").length);
         }
         else if (elementType == CHECKBOX)
         {
            nodeIndex = elementId.substring((TREEVIEW_ID + "n").length, elementId.indexOf("CheckBox"));
         }
         return nodeIndex;
    }
    
    //checks or unchecks the nested checkboxes
    function ToggleChildCheckBoxes(checkBox)
    {
        var postfix = "n";
        var childContainerId = TREEVIEW_ID + postfix + GetNodeIndex(checkBox.id, CHECKBOX) + "Nodes";
        var childContainer = document.getElementById(childContainerId);
        if (childContainer)
        {
            var childCheckBoxes = childContainer.getElementsByTagName("input");
            for (var i = 0; i < childCheckBoxes.length; i++)
            {
                childCheckBoxes[i].checked = checkBox.checked;
            }
        }
    }
    
    //unchecks the parent checkboxes if the current one is unchecked
    function ToggleParentCheckBox(checkBox)
    {
        if(checkBox.checked == false)
        {
            var parentContainer = GetParentNodeById(checkBox, TREEVIEW_ID);
            if(parentContainer) 
            {
                var parentCheckBoxId = parentContainer.id.substring(0, parentContainer.id.search("Nodes")) + "CheckBox";
                if($get(parentCheckBoxId) && $get(parentCheckBoxId).type == "checkbox") 
                {
                    $get(parentCheckBoxId).checked = false;
                    ToggleParentCheckBox($get(parentCheckBoxId));
                }
            }
        }
    }
    
    //returns the ID of the parent container if the current checkbox is unchecked
    function GetParentNodeById(element, id)
    {
        var parent = element.parentNode;
        if (parent == null)
        {
            return false;
        }
        if (parent.id.search(id) == -1)
        {
            return GetParentNodeById(parent, id);
        }
        else
        {
            return parent;
        }
    }
</script>

This code should be put to the bottom of the page, that will assign the event handler created above.

<script type="text/javascript">
    var links = document.getElementsByTagName("a");
    for (var i = 0; i < links.length; i++)
    {
        if (links[i].className == TREEVIEW_ID + "_0")
        {
            links[i].href = "javascript:ToggleCheckBox(\"" + links[i].id + "\");";
        }
    }
    
    var checkBoxes = document.getElementsByTagName("input");
    for (var i = 0; i < checkBoxes.length; i++)
    {
        if (checkBoxes[i].type == "checkbox")
        {
            $addHandler(checkBoxes[i], "click", checkBox_Click);
        }
    }
</script>

 

Although we don't use AJAX here, the code still makes uses of ASP.NET AJAX library because it provide convenient event handling classes and quick shortcuts.

The other way to solve the problem is to use CSS Friendly Control Adapters that contain a TreeView modification that has all the features we need, moreover it generates <ul> and <li> instead of tables that dramatically reduces the size of the HTML code.

The project files are located here.

Mike Borozdin

Monday, August 11, 2008

blog comments powered by Disqus