Wednesday, January 24, 2007
by Nik Kalyani
Wednesday, January 24, 2007 10:04:34 PM (Pacific Standard Time, UTC-08:00)

I needed a way to present the user with a hierarchical list of categories in an ASP.Net application. I also needed the code to be light-weight and the UI to be simple and customizable with CSS. Having AJAX functionality was not important as the number of categories in the list will not be very large. After checking-out several commercial tree components, existing JS libraries and various code snippets, I decided to write my own as I did not find anything that matched my needs.

The resulting “checkboxList” class is simple and easy to use. I have completed the Javascript coding, but still need to wire it up into an ASP.Net component. Below is the working Javascript code with some usage notes.

Usage:

checkboxList(instanceVariableName, trackingFieldId, containerId)

instanceVariableName: Name of variable with a reference to the object
trackingFieldId: The client-side ID of a text field where a list of checked item IDs will be stored delimited by “|”
containerId: The client-side ID of an HTML container element (such as DIV) where the list will be injected

Example: var myList = new checkboxList(“myList”, “trackingField”, “container”);

Adding items to the checkbox list is done using the appropriately named “add()” method as follows:

checkboxList.add(itemId, itemLabel, isChecked[, parentItemId])

Examples:
myList.add(“node1”, “My First Node”, false);
myList.add(“node2”, “My Second Node”, false);
myList.add(“node3”, “Child of First Node”, true, “node1”);

After adding nodes as desired, call the “render()” method to render the checkbox list to the browser:

myList.render();

One important thing to remember is that the “render()” method expects to already find the container element. If the browser has not yet created the container element, then the checkbox list will not be rendered. The result will look something like this:

Cblist

The text field where the values will be stored should ideally be hidden (displayed here just to show the data values). For simplicity, I decided not to include images and instead rely on CSS styling to indicate if a node has children and is expandable. There are three style classes:

node: appearance of the text label
nodeChild: appearance of the SPAN element used for a child node block
nodeParent: appended to “node” for nodes that contain child nodes

Here’s the code:

 
<style>
body
{
    font: 10pt Verdana,sans-serif;
    color: navy;
}
.node
{
    cursor: pointer;
    cursor: hand;
    display: block;
}
 
.nodeChild
{
    display: none;
    margin-left: 16px;
}
 
.nodeParent
{
    font-weight: bold;
}
 
.scrollingList
{
    height: 120px;
    width: 250px;
    overflow: auto;
    border:1px solid #e0e0e0;
}
</style>
 
<script language="JavaScript">
 
function checkboxList(instanceName, trackingElementId, containerElementId)
{
    this.instanceName = instanceName;
    this.add = addNode;
    this.render = renderCheckboxList;
    this.allNodes = new Object; 
    this.tracker = document.getElementById(trackingElementId);
    this.containerElementId = containerElementId;
}
 
function node(id, text, checked)
{
    this.id = id;
    this.text = text;
        this.checked = checked;
    this.parentId = null;
    this.show = expandParent;
    this.rootInstance = '';
}
 
function expandParent()
{
    // Expands the parent node causing a node to be displayed
    // This is automatically done when rendering to ensure
    // that all checked nodes are visible
 
    var p = this.parent;
    while(p)
    {
        var el = document.getElementById(p.id);
        el.style.display = 'block';
        p = p.parent;        
    }
}
 
 
function renderCheckboxList()
{
    // Renders all checkboxes
    var checkboxListString = '';
    for(var n in this.allNodes)
    {
        if (!this.allNodes[n].parentId)
            checkboxListString += this.allNodes[n].render(this);
    }
 
    var container = document.getElementById(this.containerElementId);
 
    if (container)
        container.innerHTML = checkboxListString;
 
    this.updateValue(true);
 
}
 
checkboxList.prototype.updateValue = function(display)
{
    // "display" controls if the UI is rendered to ensure that every checked node is
    // visible. If "display" is true, a node's show() method is called.
 
        var checkedString = "";
    for(var n in this.allNodes)
    {
        if (this.allNodes[n].checked)
        {
            if (display)
                this.allNodes[n].show();
            checkedString += (checkedString == "" ? "" : "|") + this.allNodes[n].id;
        }
    }
    this.tracker.value = checkedString;    
}
 
checkboxList.prototype.toggle = function(nodeId, checked)
{
    // If the user clicks a checkbox, the corresponding object in the associative array is updated
 
    for(var n in this.allNodes)
    {
        if (this.allNodes[n].id == nodeId)
        {
            this.allNodes[n].checked = checked;
            break;
        }    
    }
 
    // The tracking field is updated, but the nodes are not auto-expanded
    this.updateValue(false);
 
}
 
 
function addNode(id, label, checked, parentId)
{
    var n = new node(id, label, checked);
 
    // If the specified parentId node is not present, the node is
    // added to the top level
    if (this.allNodes[parentId])
        n.parentId = parentId;
    else
        n.parentId = null;
    n.rootInstance = this.instanceName;
    this.allNodes[n.id] = n;
}
 
node.prototype.render = function(root)
{
    // Renders a node to the browser
 
    
    // Obtain a list of child nodes by looking for nodes which have
    // this node's Id as their parentId
    var childNodes = new Array();
    for(var n in root.allNodes)
    {
        if (root.allNodes[n].parentId == this.id)
            childNodes[childNodes.length] = root.allNodes[n].id;
    }
 
    var numNodes = childNodes.length;
 
    // Compose the HTML string for rendering this node
    // Toggling the checkbox calls the toggle method of the root list object
 
    var nodeString = '<span class="node' + (numNodes > 0 ? ' nodeParent' : '') + '">';
    nodeString += '<input type="checkbox" id="cb' + this.id + '" ';
    nodeString += 'onClick="' + this.rootInstance + '.toggle(\'' + this.id + '\', this.checked)"';
    nodeString += (this.checked ? ' checked' : '') + '> ';
    nodeString += '<span' + (numNodes > 0 ? ' onClick="showNode(\'' + this.id + '\')"' : '') + '>' + this.text + '</span>';
    nodeString += '</span>';
 
    // If any child nodes are present, recursively render them also
    if (numNodes > 0)
    {
        nodeString += '<span class="nodeChild" id="' + this.id + '">';
        for(var j=0;j<numNodes;j++)
            nodeString += root.allNodes[childNodes[j]].render(root);
        nodeString += '</span>';
    }
    
    return nodeString;
}
 
 
function showNode(node)
{
    // When a user clicks on a node label
    // this function is called to toggle the display
 
    var objNode = document.getElementById(node).style;
    if(objNode.display=="block")
        objNode.display="none";
    else
        objNode.display="block";
}
 
 
var myCheckboxList = new checkboxList("myCheckboxList","trackingField", "scrollingList");
 
myCheckboxList.add('node1','Node 1', false);
myCheckboxList.add('node2', 'Node 2', false, 'node1');
myCheckboxList.add('node3', 'Node 3', true, 'node2');
myCheckboxList.add('node4', 'Node 4', false, 'node2');
myCheckboxList.add('node5', 'Node 5', false);
myCheckboxList.add('node6', 'Node 6', true, 'node5');
 
</script>
</head>
<body>
<p>
<input type="text" id="trackingField" style="width:400px">
</p>
 
<div id="scrollingList" class="scrollingList" />
<script>
myCheckboxList.render();
</script>

#    Comments [3] - Trackback    

Tuesday, February 27, 2007 9:50:38 AM (Pacific Standard Time, UTC-08:00)
Nice article can you provides some guidance on maybe a way to be able to use .net checkbox controls with it -
Dylan Barber
Friday, May 30, 2008 4:33:15 AM (Pacific Standard Time, UTC-08:00)
Hi,

It's very nice article.
I want to retrieve the added values from checkboxlist to a string value, how we do this?
I can't get the object of the checkboxlist, please let me know.
Anandaraman
Sunday, August 17, 2008 6:24:55 PM (Pacific Standard Time, UTC-08:00)
Thanks Nik!

this control is extremely helpful
gabazoo
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, i, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview
RSS feed
Search and Links
Bling

View Nik Kalyani's profile on LinkedIn

Contact me: nik*kalyani.com (replace "*")

TechBubble
www.flickr.com
This is a Flickr badge showing public photos from techbubble. Make your own badge here.
Statistics
Total Posts: 216
This Year: 34
This Month: 2
This Week: 0
Comments: 238
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008
Nik Kalyani
Sign In
All Content © 2008, Nik Kalyani