Mike Borozdin's Blog

A blog about programming, web and IT in general

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:

  • When you check a parent or a branch node the child nodes don't get checked
  • TreeView doesn't generate <label> tags around the nodes text, so when user click on the text next to the checkbox, the latter don't get checked

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.


Tags:
Posted by Mike Borozdin on Monday, August 11, 2008 9:34 AM GMT
  Shout it Kick it!  
Permalink | Comments (19) | Post RSSRSS comment feed

Comments

DotNetKicks.com

Monday, August 11, 2008 2:46 AM GMT

trackback

Trackback from DotNetKicks.com

ASP.NET TreeView and Checkboxes

vimal saifudin U.A.E.

Thursday, October 02, 2008 8:07 AM GMT

vimal saifudin

Excellent

Mike Borozdin Russia

Thursday, October 02, 2008 8:38 AM GMT

Mike Borozdin

Thanks Smile!

Smeat United Kingdom

Wednesday, October 15, 2008 2:47 AM GMT

Smeat

Brilliant, as soon as I realised the treeview didn't do what I needed I googled and found this page. Dropped your javascript and a scriptmanager onto my page and was up and running in less than a minute.


Smeat

Mike Borozdin Russia

Wednesday, October 15, 2008 5:37 AM GMT

Mike Borozdin

Thanks Smeat Smile!

Tahir Saudi Arabia

Sunday, November 16, 2008 11:29 PM GMT

Tahir

Hi,
first off all thkx for this very useful code,its made my life ezy.
But there one prb, my treeview is inside a AJAX update panel,and its not working with your code.
When i use ur code without update panel the parent and child check concepts works fine,
But as soon as i put the Treeview iniside an update panel after any partialpost back the child doesnot check
itself when the parent is checked.
So if any body have this solution please do let me know

thkx.

kamlesh India

Monday, January 05, 2009 9:47 PM GMT

kamlesh

Thanks, Dude
It's nice help and most usable code.
But there's one issue regording me ie; this is not working in Asp.net Pages with master page.

Thanks, if u help me to some this.
I'm waiting for your response.

Thanks.



Mike Borozdin

Tuesday, January 06, 2009 12:19 AM GMT

Mike Borozdin

Thanks,

I'll have a look.

Bo Canada

Friday, January 09, 2009 10:40 AM GMT

Bo

Great code, but i have the same issue as Tahir's
plus if the treeview is in a modalpopup, it wont work.

To kamlesh:
change your code to:
var TREEVIEW_ID = "<%=YOUR_TREE_VIEW.ClientID%>";

thank you

Bo Canada

Friday, January 09, 2009 12:18 PM GMT

Bo

Ok, I solve this problem, I want to share it to you guys:p

this because when an ajax page callback, the click eventhandler wont be added.

first of all, put  

var TREEVIEW_ID = "<%=YOUR_TREE_VIEW.ClientID%>";

on the top of treeview page.

put rest of code except code below
"This code should be put to the bottom of the page, that will assign the event handler created above."
to a new javascript file, i call it "treeview.js"

create another javascript file:
ClientEvent.js
with the following code:

var app = Sys.Application;
app.add_load(ApplicationLoad);
function ApplicationLoad(sender, args) {
    var checkBoxes = document.getElementsByTagName("input");
    for (var i = 0; i < checkBoxes.length; i++) {
        if (checkBoxes[i].type == "checkbox" && checkBoxes[i].id.search(TREEVIEW_ID) >= 0) {
            $addHandler(checkBoxes[i], "click", checkBox_Click);
        }
    }

}

finally, change your scriptmanager to:

    <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="ClientEvent.js" />    
        </Scripts>
    </asp:ScriptManager>

Geeth India

Wednesday, April 08, 2009 12:03 AM GMT

Geeth

Thanks a lot for this very useful code.

Priya India

Wednesday, April 08, 2009 2:23 AM GMT

Priya

Superb code. Thank you sooooo much.

Chaithu India

Thursday, May 28, 2009 1:29 AM GMT

Chaithu

Very good article.. Thanks a lot

Dan United Kingdom

Friday, June 05, 2009 2:55 AM GMT

Dan

Superb article, incredibly helpful. Thanks a lot!

One thing, when all children are checked the parent does not automatically recheck itself.

Perasan Thailand

Monday, June 08, 2009 12:45 AM GMT

Perasan

Nothing to say.Thank a lot.

udhaya India

Tuesday, June 23, 2009 11:25 PM GMT

udhaya

ya, i make use of it,
thank a lot

Sandip India

Thursday, August 20, 2009 1:05 AM GMT

Sandip

sorry friends but i getting an error:
$addHandler is not defined
Line 509

plz help me, how to resolve

Mike Borozdin Russia

Thursday, August 20, 2009 3:51 AM GMT

Mike Borozdin

@Sandip,

Looks like, you didn't include the ASP.NET AJAX Library that contained that function.

shima Iran

Monday, September 07, 2009 6:15 AM GMT

shima

Thanks for your very useful code. But I have a problem. my treeview is created dynamicly in code behind.
What should I do?

Comments are closed