As I had previously posted, the company I'm working for right now has purchased 5 subscriptions to the Telerik Control Suite. I've had some queries from interested developers asking how I like the controls so far and the answer is that I like them a lot!
Today I'm going to show a brief example of how we are using the treeview control to handle email contacts. We have an internal only custom mail system that allows users to keep track of contacts and organize them into folders. Being that folders and contacts are have hierarchical relationships this is the perfect use for the telerik treeview control. We also wanted to get rid of our old clunky interface and use context menus to give managing contacts a "desktop" feel on the web.
Step 1: HTML view
Here is the html attributes of the treeview control for reference. You can see how various events and paths have been set for images, context menus, etc.
id="tvwMailContacts" runat="server" OnNodeEdit="HandleNodeEdit" AllowNodeEditing="True" BeforeClientContextClick="ContextMenuClick" ContextMenuContentFile="~/XML/ContextMenus.xml" OnNodeContextClick="HandleContextClick" ImagesBaseDir="~/Images/Outlook/" CausesValidation="False"
Step 2: Setting up the context menus
Every node on the Telerik tree can be assigned to a context menu XML file, the path of which is set in the properties. Every user may add or delete contacts and folders owned by them, but there are some system level folders that they are not allowed to edit or delete. So we ended up with three types of context menus defined in xml as follows:
<?xml version="1.0" encoding="utf-8" ?>
<
ContextMenus>
<
Menu Name="UserContactFolder">
<Item Image="drafts.gif" Text="Edit Folder" PostBack="False" />
<Item Image="newFolder.gif" Text="New Folder" PostBack="True"/>
<Item Image="contact.gif" Text="New Contact" PostBack="True"/>
<Item Image="deleteMessage.gif" Text="Delete Folder" PostBack="True"/>
</Menu>
<Menu Name="SystemContactFolder">
<Item Image="newFolder.gif" Text="New Folder" PostBack="True"/>
<Item Image="contact.gif" Text="New Contact" PostBack="True"/>
</Menu>
<Menu Name="UserContact">
<Item Image="drafts.gif" Text="Edit Contact" PostBack="True" />
<Item Image="deleteMessage.gif" Text="Delete Contact" PostBack="True"/>
</Menu>
</
ContextMenus>
Notice how ourthree menu types expose different commands and you can even control whether the click causes a postback or not. Editing a contact causes a postback because there is a few fields for contact information while editing a folder name can happen on the client before it posts back. We'll get into the code for that later.
Step 3: Populating the treeview
To create the propery hierarchy for the treeview control, we are pulling two DataTables into a dataset, defining a relationship and using the GetChildRows dataset method. The code to do this is as follows:
First, we walk the rows in the folder table and find the rows where the parentFolder is null, that means they are top level folders. Once we find a top level folder we create a node and then recursively populate its children, both folders and contacts.
1 ds.Relations.Add("FolderRelation", ds.Tables(0).Columns("contactFolderID"), ds.Tables(0).Columns("parentFolder"), False)
2 ds.Relations.Add("ContactRelation", ds.Tables(0).Columns("contactFolderID"), ds.Tables(1).Columns("contactParentFolder"), False)
3
4 Dim dbRow As DataRow
5 For Each dbRow In ds.Tables(0).Rows
6 If dbRow.IsNull("parentFolder") Then
7 'populate node properties
8 Dim node As RadTreeNode = CreateContactNode(dbRow("contactFolderID"), dbRow("FolderName").ToString(), dbRow("folderImage").ToString(), True)
9
10 'Check to see it this is a user or system folder, or a contact
11 If IsDBNull(dbRow("folderOwner")) Then
12 node.ContextMenuName = "SystemContactFolder"
13 Else
14 node.ContextMenuName = "UserContactFolder"
15 End If
16
17 tree.AddNode(node)
18 RecursivelyPopulateContactFolders(dbRow, node)
19 RecursivelyPopulateContacts(dbRow, node)
20 End If
21 Next dbRow
Here is the node creation and recursive logic. You can see how the context menu name is set matching the XML file:
1 Private Sub RecursivelyPopulateContactFolders(ByVal dbRow As DataRow, ByVal node As RadTreeNode)
2 Dim childRow As DataRow
3 For Each childRow In dbRow.GetChildRows("FolderRelation")
4 'populate node properties
5 Dim childNode As RadTreeNode = CreateContactNode(childRow("contactFolderID"), childRow("FolderName").ToString(), childRow("folderImage").ToString(), True)
6
7 'Null owners are system folders
8 If IsDBNull(childRow("folderOwner")) Then
9 childNode.ContextMenuName = "SystemContactFolder"
10 Else
11 childNode.ContextMenuName = "UserContactFolder"
12 End If
13
14 node.AddNode(childNode)
15 RecursivelyPopulateContactFolders(childRow, childNode)
16 RecursivelyPopulateContacts(childRow, childNode)
17 Next childRow
18 End Sub
19
20 Private Sub RecursivelyPopulateContacts(ByVal dbRow As DataRow, ByVal node As RadTreeNode)
21 Dim childRow As DataRow
22 For Each childRow In dbRow.GetChildRows("ContactRelation")
23 'populate node properties
24 Dim childNode As RadTreeNode = CreateContactNode(childRow("emailContactID"), childRow("Alias").ToString(), "contact.gif", False)
25 childNode.ContextMenuName = "UserContact"
26
27 node.AddNode(childNode)
28 Next childRow
29 End Sub
30
31 Private Function CreateContactNode(ByVal ID As Integer, ByVal displayText As String, ByVal nodeImage As String, ByVal expanded As Boolean, Optional ByVal contextMenuName As String = "") As RadTreeNode
32 Dim node As New RadTreeNode(displayText)
33 node.Image = nodeImage
34 node.Expanded = True
35 node.ID = ID.ToString()
36 Return node
37 End Function
The relational data looks like this:

Step 4: Testing Context Menus
We can now fire up the application and use the context menus for the 3 types of items (contact, system folder, and user folder). Here's a shot of it in action:



Step 5: Client Side Editing
Notice from the telerik control html that we defined a javascript function for to fire on context clicks. We want to capture the edit folder click and allow them to do a client side edit and then post the edit to the server. All we have to do is define a javascript function (specified in the telerik html).
function ContextMenuClick(node, itemText) {
if (itemText == "Edit Folder") {
node.StartEdit();
}
return true;
}
Here's the result:

Step 6: Server Side Code
All of the save/delete/update logic resides on the server in the subroutine specified in the telerik control html. Here is the code to handle clicks based on their context menu type. We have one for the postback menu items and one for the edit item. This is very easy and confortable to do because the NodeEvents argument behaves very similarily to an item command:
1 Protected Sub HandleContextClick(ByVal sender As Object, ByVal NodeEvents As RadTreeNodeEventArgs)
2 Try
3 Dim contextCommand As String = NodeEvents.ContextMenuItemText
4 Dim currentNode As RadTreeNode = NodeEvents.NodeClicked
5
6 Dim contactFolder As New SchoolOne.Mail.EmailContactFolder
7 Select Case contextCommand
8 Case "Delete Folder"
9 If NodeEvents.NodeClicked.Nodes.Count > 0 Then
10 Throw New Exception("Can not delete a folder with items in it!")
11 End If
12 contactFolder.ID = Integer.Parse(NodeEvents.NodeClicked.ID)
13 contactFolder = MailDomainManager.Load(contactFolder)
14 MailDomainManager.Delete(contactFolder)
15
16 Case "New Folder"
17 contactFolder.Owner = ThisUser.InternalEmail
18 contactFolder.Name = "New Folder"
19 contactFolder.ParentFolder = Integer.Parse(NodeEvents.NodeClicked.ID)
20 contactFolder.Image = "folder.gif"
21 contactFolder = MailDomainManager.Save(contactFolder)
22
23 Case "New Contact"
24 ShowControls()
25 ParentFolderID.Text = NodeEvents.NodeClicked.ID
26
27 Case "Edit Contact"
28 ShowControls()
29 EmailContactID.Text = NodeEvents.NodeClicked.ID
30 Dim emailContact As New SchoolOne.Mail.EmailContact(Integer.Parse(EmailContactID.Text))
31 emailcontact = MailDomainManager.Load(emailcontact)
32 txtContactEmail.Text = emailcontact.EmailAddress
33 txtAlias.Text = emailcontact.ContactAlias
34
35 Case "Delete Contact"
36 Dim emailContact As New SchoolOne.Mail.EmailContact(Integer.Parse(NodeEvents.NodeClicked.ID))
37 MailDomainManager.Delete(emailContact)
38 End Select
39
40 LoadContacts(tvwMailContacts)
41 Catch ex As Exception
42 WriteException(ex, ExceptionLog.None)
43 End Try
44
45 End Sub
46
47 Protected Sub HandleNodeEdit(ByVal sender As Object, ByVal NodeEvents As RadTreeNodeEventArgs)
48 'check if it is a folder or a contact
49 Try
50 If NodeEvents.NodeEdited.Image = "folder.gif" Then
51 Dim contactFolder As New SchoolOne.Mail.EmailContactFolder
52 contactFolder.ID = Integer.Parse(NodeEvents.NodeEdited.ID)
53 contactFolder = MailDomainManager.Load(contactFolder)
54 contactFolder.Name = NodeEvents.NewText
55
56 contactFolder = MailDomainManager.Save(contactFolder)
57 End If
58
59 LoadContacts(tvwMailContacts)
60 Catch ex As Exception
61 WriteException(ex, ExceptionLog.None)
62 End Try
63 End Sub
Conclusion
So far I am very impressed with the ease of configuring and coding in the Treeview control. I will post more feedback as I get into the next controls: Editor, Spellcheck, and PanelBar.