Bookmark Me | RSS | RSS | Comments RSS

Using TreeView inside AJAX UpdatePanel

Saturday, June 27, 2009

Microsoft does not support TreeView Control inside AJAX:UpdatePanel. So there are lots of issues if one need to do the same. One of my teams needed to use the TreeView inside the UpdatePanel. The core of the application is around some hierarchical documentation. As the user interaction with the system mostly through this hierarchical data, TreeView with check boxes is the most obvious selection. However, the pain began as the development progressed and changes in requirements started pouring in. One option was for clientside population of treeview. But some dynamic changes happening to the underlying hierarchical data (by concurrent users), we needed to repopulate the tree from the database. With growing data volume, clientside population had to be abandoned. But the TreeView started giving endless sitting with the code as it started behaving weirdly at most of the times.

Here are some of the key issues and resolutions of some of them. Some are yet to be resolved, however.

1. The SelectedNode property is lost in successive postbacks. The scenario is easy. Click any TreeNode. In the coddebehind, you get the TreeView.SelectedNode. But on any successive postback where the postback is caused by anything other than a TreeNode click, the SelectedNode property is NULL.

Resolution: Use your own ViewState to manage. Following is the code segment:

    #region ViewState handling for selected node
    //Added by Kangkan - on June 04 2009
    // As the selected Node is lost on second or more postback
    // if the treeview is inside the update panel.
    protected override object SaveViewState()
    {
        if (TViewDeviceHeirarchy.SelectedNode != null)
        {
            ViewState["SelectedNodePath"] = TViewDeviceHeirarchy.SelectedNode.ValuePath;
        }
        return base.SaveViewState();
    }

    protected void Page_PreLoad(object sender, EventArgs e)
    {
        GetSelectedNodeFromViewState();
    }

    private void GetSelectedNodeFromViewState()
    {
        if (ViewState["SelectedNodePath"] != null)
        {
            TreeNode node = TViewDeviceHeirarchy.FindNode(ViewState["SelectedNodePath"].ToString());
            if (node != null)
                node.Select();
            else
                ViewState["SelectedNodePath"] = null;
        }
    }

    public void ClearSelectedNodeViewState()
    {
        ViewState.Remove("SelectedNodePath");
        if (TViewDeviceHeirarchy.SelectedNode != null)
            TViewDeviceHeirarchy.SelectedNode.Selected = false;
    }
    #endregion ViewState handling for selected node

2. The CheckedNodes collection is null in successive postbacks. The scenario is easy. Check any checkbox associated with a TreeNode. In the coddebehind, you get the TreeView.CheckedNodes collection. But on any successive postback where there is no change in the checkedNodes, the CheckedNodes collection returns 0 count. And in this point of time, though the TreeNodes are checked at the client (browser), a FindNode at times fail to locate the node and if it locates, the TreeNode.Checked property is also at times false. It was a nightmare to continue with such a piece of code. Thought of using the same method as that used for persisting the SelectedNode. There are issues with that. So we relied upon Session variable. Here is the code segment for the same:

    #region CheckNodes handling
    //To handle loss of checked Nodes on repeatitive postbacks
    //  SetCheckedNodesToSession sets the nodes'ValuePath to session
    //  GetCheckedNodesFromSession gets the TreeNode Collection from the session
    //The session variable Session["CheckedNode"] is used.
    //The same need to be cleared (set to null) once the usage is over.
    //-----------------------------------------------------------------

    /// <summary>
    /// SetCheckedNodesToSession: Sets the CheckedNodes' ValuePath to a Session Object.
    /// ValuePath of each CheckedNode is concatenated to a string and the string is saved.
    /// Session["CheckedNode"] is used for saving the ValuePath string.
    /// </summary>
    private void SetCheckedNodesToSession()
    {
        if (TViewDeviceHeirarchy.CheckedNodes.Count > 0)
        {
            StringBuilder checks = new StringBuilder();
            for (int i = 0; i < TViewDeviceHeirarchy.CheckedNodes.Count; i++)
            {
                checks.Append(TViewDeviceHeirarchy.CheckedNodes[i].ValuePath + "|");
            }
            Session["CheckedNode"] = checks.ToString().Remove(checks.ToString().Length - 1, 1);
        }
    }

    /// <summary>
    /// Return TreeNodeCollection from the Session
    /// Session["CheckedNode"] is used for saving the ValuePath string.
    /// </summary>
    /// <returns>TreeNodeCollection as saved into the session.</returns>
    private TreeNodeCollection GetCheckedNodesFromSession()
    {
        TreeNodeCollection ColNodes = new TreeNodeCollection();
        if (Session["CheckedNode"] != null)
        {
            string checks = Session["CheckedNode"].ToString();
            string[] CheckArray = checks.Split(new char[] { '|' });
            TreeNode node;
            foreach (string s in CheckArray)
            {
                node = TViewDeviceHeirarchy.FindNode(s);
                if (node != null)
                {
                    ColNodes.Add(node);
                    //node.Checked = true;
                }
            }
        }
        return ColNodes;
    }

    #endregion CheckNodes handling


3. SelectedNodeChanged event does not fire! The usage scenario include change of one node from one parent to another parent node. So what happens is that once we complete the task, we clear the nodes of the tree [TreeView.Nodes.clear()] and rebind the tree. We bind the tree to the first level only and set the nodes to PopulateOnDemand=true for the nodes that have child nodes. At this moment, if we again click on any tree node, at times it does not fire any postback. Rather the element just vanishes(?) from the treeview. If I reload the page, we can see the element at it works fine. To get rid of this issue, I use to reload the page at the end of certain usage scenario. But the pain is how to let the user know the satus of what happened at the end of the usage? Again the use of Session variable. What we have done is shown below:

At the end of any such usage scenario, put the message to the user in a session variable and reload the page:



    public void SetReloadStatus(string message, bool IsError)
    {
        Session["ReloadMessage"] = message;
        Session["ReloadStatus"] = IsError.ToString();
    }



At the load of the page, check for the existence of any session variable for pending tasks and complete accordingly:


    public void ShowReloadStatus()
    {
        if (Session["ReloadStatus"] != null)
        {
            bool boolVal = Convert.ToBoolean(Session["ReloadStatus"].ToString());
            if (boolVal)
                Infobar1.error = Session["ReloadMessage"].ToString();
            else
                Infobar1.info = Session["ReloadMessage"].ToString();
            Session["ReloadStatus"] = null;
            Session["CheckedNode"] = null;
        }
    }


I am aware that the methods applied are not good, rather I shall say not at all meaningful from programming point of view. But till Microsoft does not provide better solutions or I migrate to some other wiser control that works properly, these are some work arounds that we are sticking to. I shall request you to provide your valuable feedback and further suggestions on this.

 

 

Filed Under: ASP.NET, Learning

Add comment

biuquote
Loading