Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

An ASP.NET AJAX TreeView control with templates

4.74/5 (25 votes)
7 Dec 2008Ms-PL6 min read 238.1K   7.5K  
An ASP.NET AJAX TreeView control with templates, event binding, client and server events, and more...

TreeView2.png Odyssey TreeView3.png

Introduction

OdcTreeView is a templated ASP.NET AJAX Server Control with hierarchical data binding.

This article covers the following topics:

  • Static nodes
  • Populate on demand
  • IHierarchicalDataSource binding
  • Templates
  • NodeBinding event
  • AJAX server controls
  • JavaScript namespaces, classes, properties, and custom events
  • Custom style

Static Nodes

Like the ASP.NET TreeView, you can either use a data source to retrieve the nodes, or you simply specify them in the ASPX source like this:

ASP.NET
<odc:OdcTreeView ID="OdcTreeView1" 
    runat="server" EnableViewState="true"> 
<Nodes>
<odc:OdcTreeNode Text="1">
<odc:OdcTreeNode Text="1.1" ImageUrl="~/ColorHS.png" />
<odc:OdcTreeNode Text="1.2" />
</odc:OdcTreeNode>
<odc:OdcTreeNode Text="2" />
<odc:OdcTreeNode Text="3">
<odc:OdcTreeNode Text="3.1" />
<odc:OdcTreeNode Text="3.2" />
<odc:OdcTreeNode Text="3.3">
<odc:OdcTreeNode Text="3.3.1" />
<odc:OdcTreeNode Text="3.3.2" />
<odc:OdcTreeNode Text="3.3.3" />
<odc:OdcTreeNode Text="3.3.4" ImageUrl="~/ColorHS.png" />
<odc:OdcTreeNode Text="3.3.5" ShowCheckBox="true" />
<odc:OdcTreeNode Text="3.3.6" 
  ShowCheckBox="true" IsChecked="true" />
</odc:OdcTreeNode>
</odc:OdcTreeNode>
<odc:OdcTreeNode Text="4">
<odc:OdcTreeNode Text="4.1" />
<odc:OdcTreeNode Text="4.2" 
  PopulateOnDemand="true" IsExpanded="true" />
<odc:OdcTreeNode Text="4.3" />
<odc:OdcTreeNode Text="4.4" />
<odc:OdcTreeNode Text="4.5" />
</odc:OdcTreeNode>
</Nodes>
</odc:OdcTreeView>

Populate on Demand

In conjunction with static nodes, you can also enable population on demand, which means that you don’t need to declare all possible nodes at once, and add child nodes to a parent node only when the parent expands for the very first time. Therefore, you simply set the PopulateOnDemand property of an OdcTreeNode to true, which causes the OdcTreeView to raise a NodePopulate event. This event can be implemented like so:

C#
protected void OdcTreeView1_NodePopulate(object sender, OdcTreeNodeEventArgs e)
{
    OdcTreeNode node = e.Node;
    node.PopulateOnDemand = false;
    for (int i = 1; i < 6; i++)
    {
        OdcTreeNode sub = new OdcTreeNode();
        sub.Text = node.Text + "." + i.ToString();
        sub.IsExpanded = false;
        sub.PopulateOnDemand = i==3;
        node.ChildNodes.Add(sub);
    }
}

Note that this method also sets the PopulateOnDemand to false, so it will not cause another NodePopulate when the node gets collapsed and later expanded, since it stores it state in the View State together with the populated tree nodes.

Data Binding

Of course, OdcTreeNode also supports data binding with any IHierarchicalDataSource. You can fighter declare a DataSourceID with the name of a DataSource that implements IHierarchicalDataSource, or set the DataSource property to any IHierarchicalDataSource at runtime. Additionally, you need to declare at least one OdcTreeNodeBinding class unless you don’t use a customized node template to bind the embedded controls directly. This class specifies how to bind the properties of a data item with the properties of a OdcTreeNodeItem, or it declares what default values a OdcTeeNodeItem should be assigned to. If you don’t set the OdcTreeNodeBinding.Level to a value, then this binding is used as the template for all nodes. If you specify a level, however, this OdcTreeNodeBinding only affects the nodes that are in this hierarchical level.

This example demonstrates declarative data binding with an XML file:

ASP.NET
<odc:OdcTreeView ID="OdcTreeView1" 
  runat="server" DataSourceID="XmlDataSource1">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Level="0" TextField="Title"/>
<odc:OdcTreeNodeBinding Level="1" TextField="Title" />
<odc:OdcTreeNodeBinding Level="2" TextField="Title" 
  ShowCheckBox="true" IsChecked="true" />
<odc:OdcTreeNodeBinding Level="3" TextField="Value" 
  ShowCheckBox="true" IsChecked="false" />
</TreeNodeBindings>
</odc:OdcTreeView>
<asp:XmlDataSource ID="XmlDataSource1" runat="server" 
  DataFile="~/Xml.xml"></asp:XmlDataSource>

Templates

OdcTreeView knows three different templates:

  • NodeTemplate - a data template for each node.
  • EditNodeTemplate - a data template for the node that is in edit mode, which can be only one.
  • ContextMenuTemplate - a template for a context menu that appears when you right click the mouse on a node (unless you are not working with Opera). If you don’t specify a NodeTemplate or EditNodeTemplate, OdcTreeView generates a default template.

The following example demonstrates how to use templates:

ASP.NET
<odc:OdcTreeView ID="treeView" runat="server" AutoPostBack="False" >
<ContextMenuTemplate>
<asp:LinkButton ID="btnOkay" runat="server" CommandName="ok" Text="Okay" /><br />
<asp:LinkButton ID="btnCancel" runat="server" CommandName="cancel" Text="Cancel" />
</ContextMenuTemplate>
<EditNodeTemplate>
<asp:TextBox runat="server" ID="tbText" Text='<%# Bind("Text") %>' />
<asp:LinkButton ID="btnRename" runat="server" CommandName="rename" Text="Rename" />
<asp:LinkButton ID="btnCancel" runat="server" CommandName="cancel" Text="Cancel" />
</EditNodeTemplate>
<NodeTemplate>
<%# Container.Node.Text %><asp:LinkButton 
  Style="padding-left: 4px" ID="btnAdd" runat="server"
CommandName="add" Text="Add" />
<asp:LinkButton ID="btnRemove" runat="server" CommandName="remove" Text="Remove" />
<asp:LinkButton ID="btnEdit" runat="server" CommandName="edit" Text="Edit" />
</NodeTemplate>
</odc:OdcTreeView>

As you can see, there are two ways used to bind the OdcTreeNode.Text property:

  1. <%# Bind("Text") %>
  2. Using Bind() enables you to access the properties of the data bound item, rather than the OdcTreeNode itself, so this means that the node must be bound to an object that has a Text property. If not data bound, Bind() retrieves the properties from the node itself.

  3. <%# Container.Node.Text %>
  4. This is the usage to access the properties of the tree node directly. Since Container is of type OdcTreeNodeContainer that exposes Node, DataItem would be the data bound object.

OdcTreeNodeBinding also offers a NodeTemplate to specify. This enables you to modify the look of nodes of different levels. The following example demonstrates how to apply a button to the root node:

ASP.NET
<odc:OdcTreeView ID="OdcTreeView1" runat="server" DataSourceID="XmlDataSource1">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Level="0" TextField="Title">
<NodeTemplate>
<asp:Button runat="server" Text="this is the root" UseSubmitBehavior="False" />
</NodeTemplate>
</odc:OdcTreeNodeBinding>
<odc:OdcTreeNodeBinding Level="1" TextField="Title" />
<odc:OdcTreeNodeBinding Level="2" TextField="Title" 
   ShowCheckBox="true" IsChecked="true" />
<odc:OdcTreeNodeBinding Level="3" TextField="Value" 
   ShowCheckBox="true" IsChecked="false" />
</TreeNodeBindings>
</odc:OdcTreeView>

NodeBinding Event

It is even possible to attach any OdcTreeNodeBinding of the NodeBindings collection to a node determined in an event named NodeBinding. This works both for data binding and static nodes, or population on demand. Thus, you can vary the template of a node by using any terms you want.

The following example illustrates how to use NodeBinding:

ASPX part:
ASP.NET
<odc:OdcTreeView ID="OdcTreeView1" runat="server" EnableViewState="true" Font-Size="9"
Font-Names="Arial" EnableDragDrop="true" AutoPostBack="false" DisableTextSelection="true"
EnableClientExpand="true" AllowNodeEditing="false" ExpandDepth="7" 
onnodebinding="OdcTreeView1_NodeBinding" 
onnodepopulate="OdcTreeView1_NodePopulate">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Name="Root" ShowCheckBox="true">
<NodeTemplate>
<asp:Label ID="Label2" isText="true" runat="server" 
   Text="<%# Container.Node.Text %>" />
</NodeTemplate>
</odc:OdcTreeNodeBinding>
</TreeNodeBindings>
Code part:
C#
/// <summary>
/// Determine what OdcTreeNodeBinding to apply for for a node.
/// </summary>
protected void OdcTreeView1_NodeBinding(object sender, 
               Odyssey.Web.TreeView.OdcTreeNodeBindingEventArgs e)
{
OdcTreeNode node = e.Node;
// if the node has child nodes, apply a different NodeBinding
// that contains a different node template to the node:
if (node.HasChildNodes) e.Binding = e.Bindings.GetNamedBinding("Root");
}

AJAX Server Control

OdcTreeView is an AJAX Server Control, which means that it requires a ScriptManager on the page. With AJAX, the tree view can perform many operations directly in the browser without requiring any postback. So, it is possible to select a node, and expand and collapse nodes completely inside the browser. It is even possible to edit the text of a node in the browser if you set AllowNodeEditing to true. The OdcTreeView adds a hidden field to the page, and registers it to the client part of the control. The client uses JavaScript to update the changes to the hidden field, and on a postback, the server side reads the value of the hidden field and updates the values to reflect the changes that were made at the client side. However, if you want to have a postback when a node is selected, edited, expanded, or collapsed, then you must set the AutoPostBack to false. Note that a postback always occurs when a node gets expanded that has PopulateOnDemand set to true.

JavaScript Namespaces, Classes, Properties, and Custom Events

The client part of the OdcTreeView contains of a JavaScript class named TreeViewControl, a TreeNode class, and a TreeContextMenuEventArgs class. All classes belong to the Odyssey.Web namespace. I don’t want to go in to the details of how to make JavaScript look like C# having namespaces, classes, interfaces, properties, and events, since there are a lot of good books that capture that functionality. The TreeViewControl is the client part of the AJAX control. It catches HTML events, and performs the required postbacks depending on what event has been raised. You can also specify the client events for the OdcTreeView that execute not at the server side, but at the client side by calling a JavaScript function. The following events are currently available:

  • ClientContextMenuOpening(treeView, e);
  • This event occurs before the context menu opens, and allows you to modify the look or behavior of the context menu, and exposes two parameters:

    • treeView of type Odyssey.Web.TreeViewControl.
    • e of type Odyssey.Web.TreeContextMenuEventArgs. e contains the node for which the context menu should be opened and the HTML element that covers the context menu itself. Since e is derived from CancelEventArgs, you can also cancel to open the context menu.
  • ClientNodeSelectionChanged(node, e);
  • Occurs when a node is selected.

  • ClientNodeExpanded(node,e);
  • Occurs when a node is expanded.

  • ClientNodeCollapsed(node, e);
  • Occurs when a node is collapsed.

  • ClientNodeCheckedChanged(node, e);
  • Occurs when a node has changed its get_checked() state.

  • ClienEditModeChanged(node, e);
  • Occurs when a node has changed its isEditable() state.

The following snippet illustrates how to use the client events and how to retrieve properties from the AJAX classes:

ASP.NET
<odc:OdcTreeView ID="OdcTreeView1" runat="server" EnableViewState="true" 
ClientNodeTextChanged="nodeTextChanged"
ClientNodeExpanded = "expanded"
ClientContextMenuOpening="myContextMenu" 
ClientNodeCollapsed="collapsed"
ClientNodeSelectionChanged="nodeSelected"
 


</form>
<script type="text/javascript">
function expanded(node, e) {
if (node.getText()>="4")
  alert("Expanded: "+node.getText());
}


function collapsed(node, e) {
if (node.getText() >= "4")
  alert("Collapsed: " + node.getText());
}
 

function nodeTextChanged(node, e) {
var txt = node.getText();
var e = document.getElementsByName("custom");
e[0].innerHTML = "changed: "+txt;

}

function nodeSelected(node, e) {
var value = node.get_selected();
if (value) {
//node.setText("OK");
var txt = node.getText();
var e = document.getElementsByName("custom");
e[0].innerHTML = "selected: " + txt;
}
}
 
function myContextMenu(tree, e) {
var cm = e.menuElement;
var node = e.node;
var text = node.getText();
if (node.isFirst()) {
e.set_cancel(true);
alert("no menu for the first item!");
}
if (text == "3.3.1")
  cm.style.backgroundColor = 
     "Red"; else cm.style.backgroundColor = "";
}
</script>
</body>
</html>

Custom Style

Unlike the ASP.NET TreeView which uses inline styles to render a table to look like a tree, OdcTreeView uses the <ul> and <li> tags that natively represent a tree view and completely relies on style sheets to manipulate the look. By default, OdcTreeView uses "odcTreeView" as the base style class which is included as an embedded resource, and contains all the necessary styles for rendering, including background images to be attached, like expand and collapse buttons. If you want to customize the style, you simply need to copy the TreeView.css from the source code, rename all .odcTreeView occurrences with your own class name, add a reference to your style sheet to the page, and set the ClassName property of the OdcTreeView to that name. For the sake of smaller HTML code, OdcTreeView uses very short names for class names of sub elements, like class="ul". You might think that this could lead to collisions with other controls that might decide to use the same short name, but since the style sheet always uses the base class in conjunction with the sub class, this risk is eliminated:

CSS
.odcTreeView
{
    clear:both;
    vertical-align:top;
    padding:2px;
}


.odcTreeView .ul
{
    list-style-type: none;
    list-style-image: none;
    list-style-position: outside;
    padding: 0px 0px 0px 20px;
    margin:0px;
}

.odcTreeView .Ln
{
    background-image: url('<%=WebResource("Odyssey.Web.TreeView.images.line.gif")%>');
    background-repeat:repeat-y;
    background-position: 0px 0px;
}

The HTML code would look like this:

HTML
<div class="odcTreeView" id="OdcTreeView1" EnableClientExpand="true">
<ul class="ul">
<li class="Mid" key="1"><div class="div">
<span class="Collapse" event="collapse"></span>
<span event="click" class="span" id="OdcTreeView1_K1">
<img src="ColorHS.png" style="border-width:0px;" />
<span isText="true">1</span>

Points of Interest

The source code contains how to create an AJAX server control, how to add client properties and client events to the control, how to read from an IHierarchicalDataSource, how to implement a DataTemplate, and many more.

History

This is the initial release.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)