Monday, May 3, 2010

Tracking a Telerik RadTreeView via the UniqueID Property of Each Node

The large ASP.Net project I've been working on makes extensive use of Telerik's excellent TreeView control.  Here's an example of its implementation:


It's important to note that the Save button you see on the toolbar is used for saving all changes on the page as a batch.  There is no need for a user to press "OK" (or equivalent) after changes on each node.  To accomplish this was a little tricky because the user has the ability to make one or more changes on a given record (ie. the controls on the right side) and then click on another node or go to another module or close the browser altogether.  In all of these cases the aforementioned changes are correctly recorded, associated with the appropriate node.

I solved this by introducing a modular level property called "CurrentNodeID".  It keeps track of the currently selected node.  There's also an associated property called "CurrentNode".  It uses CurrentNodeID as a lookup value to find the currently selected node.

To ensure that each CurrentNodeID was unique, I initially just generated a new GUID and stored it in each node's "id" attribute.  This worked very well.  It was especially easy to find find the current node with a statement like this:

treeView.FindNodeByAttribute("id", CurrentNodeID)

It's also important to realize that I'm not populating the entire tree at once but only populating on demand.  To accomplish this I originally had each node's "ExpandMode" property set to "ServerSide".  Configured this way, finding a node via its "id" attribute property was very straightforward.

But more recently I've been experimenting with a newer ExpandMode property value: ServerSideCallBack  It's very powerful, using AJAX to perform a much quicker population of child nodes.  But testing quickly revealed that once the ExpandMode property was changed this way, the "id" attribute was no longer visible.  An inquiry with Telerik revealed that perhaps I should use an attribute name other than "id", as it's already the name of a node property.

Rather than go down that road I instead went back to basics and asked myself why I was using the attribute approach in the first place, for I had long known that each node's "UniqueID" property had a unique value as well.  So I set about to drop the use of the "id" attribute and instead use this new approach.  When it came to the "PreviousNodeID", rather than storing the randomly generated GUID in it, instead I'd just store the UniqueID value.  This change worked very well except for two things:
  1. You can search a treeview for a node via its attribute value but there's no direct way to search for a node via its UniqueID value.
  2. When you drag & drop a node, its UniqueID value changes.

To resolve issue #1 I had to create a new method.  Here is that method, along with how I've defined the CurrentNodeID and CurrentNode properties:

  /// References the value of the UniqueID property of the most current treeview node.
    public string CurrentNodeID
    {
      get
      {
        if (ViewState["CurrentNodeID"] == null)
          return null;

        return ViewState["CurrentNodeID"].ToString();
      }

      set
      {
        ViewState["CurrentNodeID"] = value;
      }
    }


    /// References the most current treeview node.
    public RadTreeNode CurrentNode
    {
      get
      {
        if (CurrentNodeID == null)
          return null;
      
        return FindNodeByUniqueID(treeViewMain, CurrentNodeID);
      }
    }


    /// Every node has a Unique ID value.  There's no built-in method to locate
    /// a node in a treeview via its Unique ID.  This method accomplishes that.
    public static RadTreeNode FindNodeByUniqueID(RadTreeView treeView, string uniqueID)
    {
      RadTreeNode foundNode = null;

      int nodeCount = treeView.GetAllNodes().Count;
      RadTreeNode[] allNodes = new RadTreeNode[nodeCount];
      treeView.GetAllNodes().CopyTo(allNodes, 0);

      foreach (RadTreeNode node in allNodes)
      {
        if (node.UniqueID == uniqueID)
        {
          foundNode = node;
          break;
        }
      }

      return foundNode;
    }


To resolve issue #2, one must just reset the value of PreviousNodeID after a node is moved:

PreviousNodeID = node.UniqueID

I now have a UI that works faster and there ends up being a bit less code than before.  That's a true Win-Win!