When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips

Sections:
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
XML Info
Information:
Feedback
Author an Article
ASP ASP.NET ASP FAQs Message Board Feedback
Print this page.
Published: Wednesday, February 20, 2002

Enhancing the Dynamic Tree Menu System, Part 3

By Sam M. Reynoso


  • Read Part 1
    Part 2

  • In Part 2 we looked at how to support loading the tree content data from a database table. In this part we'll look at how to conditionally load data (that is, only load data when the user clicks the menu item) and how to implement folder memory in a way that each folder can remember its previous state.

    - continued -

    Conditional Data Loading
    On a similar note, many developers reported that they were dealing with navigation trees containing hundreds of items and requested a way to load folder data a bit at a time. Since our navigation tree was put together using data loaded from a database instead of a static XML file, we have more control over how the folders get populated.

    In the sample code, Conditional Data Loading has been applied to the last folder in the tree ("History"). To demonstrate that no data is initially being loaded for this item, take a look at the HTML source immediately after loading the page. This will reveal that there are no items underneath the history folder. However, clicking the plus sign on the history folder fires off a chain of events that cause it to load its contents.

    Behind the scenes, the History folder is tied to a Javascript function that causes the menu.asp page to be submitted when that folder is opened:

      if (srcElement.name == 'LoadOnDemand')
      .
      .
      .
        document.frmMenu.submit();
    

    After the page is submitted, the ASP code queries the database for the tree data like before, but this time it also loads data corresponding to the clicked folder. The newly populated XML document object now includes the data for the clicked folder's items, and the new HTML page reflects this.

    Let's take a closer look at what happens when a user clicks a folder configured for Conditional Data Loading. First, loading additional data requires a trip to the server in order to retrieve the data. To make a submit possible, it is necessary to encapsulate the entire tree between a set of HTML <form> tags.

    A submit will occur any time a user opens a folder that is configured for Conditional Data Loading. The Javascript function doChangeTree() makes this determination by checking the name of the clicked folder's <img> tag. If the name is LoadOnDemand then the navigation tree is submitted.

      function doChangeTree(e, arClickedElementID, arAffectedMenuItemID)
        {
          var targetID, srcElement, targetElement;
          srcElement = e;
          .
          .
          .  
          if (srcElement.name == 'LoadOnDemand')
          {
            //We submit the menu only if the tree is being expanded. 
            if (targetElement.style.display == "")
            {
              .
              .
              .
                      
              document.frmMenu.submit();
              .
              .
              .
    

    On the server side, the ASP code determines if Conditional Data Loading is required by checking if the form was submitted or not. If it was, the XML loader function fnLoadXMLData() rebuilds the XML document object to include the data for the history folder.

    if (Request.Form <> "") then
      bLoadSubItems = true
    else
      bLoadSubItems = false
    end if
    
    bLoaded = fnLoadXMLData(objDocument, bLoadSubItems)
    

    The second parameter of fnLoadXMLData() is a boolean that indicates whether or not the history folder data should be loaded. This value is true whenever a form has been submitted. For the sake of simplicity, this example applies Conditional Data Loading only to a single folder. However, it would be easy to modify the code to apply similar functionality to other folders in the tree.

    Folder Memory
    The final enhancement, Folder Memory, goes hand-in-hand with Conditional Data Loading discussed above. It would make no sense to have a feature like Conditional Data Loading if all the folders in the tree defaulted back to their original closed state each time the tree was rebuilt. The Folder Memory feature gives the navigation tree the ability to "remember" the open/closed state of each of its folders. Note that this memory only works when the menu is submitted, not when it is reloaded.

    To "remember" the state of its folders, the tree must record each time a folder is open or closed. If the folder is opened, fnAddItem() is called to append the ID of the clicked folder to a comma-delimited list stored in a hidden HTML input control (hdnOpenFolders). If the folder is closed, fnRemoveItem() takes care of removing the appropriate ID from the same hidden control. These functions are shown below:

    //Adds the current element ID to a string stored in a hidden HTML field.
    //Only adds the ID if it is not already in there
    function fnAddItem(objField, sElementID)
    {
      var sCurrValue = objField.value;
    
      if (sCurrValue.indexOf(sElementID) == -1)
        objField.value = objField.value + ',' + sElementID;
    }
    
    //Removes a specific element ID from a string stored in a hidden HTML field.
    function fnRemoveItem(objField, sElementID)
    {
      var sCurrValue = objField.value;
      var arValues = sCurrValue.split(',');
      var arNewValues = new Array(0);
      var x=0;
        
      for (i=0; i < arValues.length; i++)
        if (arValues[i] != sElementID)
        {
          arNewValues[x] = arValues[i];
          x++;
        }  
        
      sCurrValue = arNewValues.join(',');
      objField.value = sCurrValue;
    }
    

    Essentially, hdnOpenFolders contains the IDs of folders that are open. As long as this list is properly maintained, we can feel confident that it accurately reflects the current state of the folders in our navigation menu. The IDs in hdnOpenFolders are used by the ASP routine DisplayNode() as it builds the HTML for the menu. After the XML document object is loaded with data, the ASP subroutine DisplayNode() is called recursively to build the navigation menu.

    Each time it examines a new node of XML data, it compares the list of open folder IDs to the ID it is going to assign to the current node. If they match, the folder is displayed as open, otherwise it is displayed closed. The ASP function fnGetFolderStatus() performs this simple check using the following lines of code:

    if (instr(sOpenFolders, CStr(iID))) then
      bReturn = true
    end if
    

    The result of this evaluation is then sent as a parameter to the the fnChooseIcon() function which returns the name of the appropriate .gif to use for the folder. This feature is the best example of how we can use HTML <form> tags, hidden <input> tags, and the Javascript submit() method to pass data from client-side Javascript routines to server-side ASP routines. The Folder Memory feature would not be possible without this level of interaction.

    Conclusion
    There is one last modification that needs mentioning. In the original sample code, the dynamic HTML generated for the navigation tree was formatted using tabs and spaces to be easily readable. This works fine for small menus but presented problems when menus contained hundreds of items. The problem was that larger menus required more HTML code which in turn had to be transmitted downstream to the browser, resulting in slower download times.

    To get around this problem, the current version of the sample code has been redesigned to contain a minimum of characters. The rendered HTML page is now smaller in bytes, but more difficult to read. Examining the source code will reveal a block of code sandwiched between open and close <form> tags. This is because all carriage returns, blank lines, unnecessary spaces, tabs, quotes, etc. were removed. The visitor will appreciate the quick download, but it may present problems for a developer attempting to debug his/her tree.

    There have been many other minor modifications to the original sample code. In some cases Javascript functions were rewritten to not use global variables. Mostly these changes consisted of rewriting ASP and Javascript routines and files to be more modular. Instead of putting everything in one file, separate .css, .js, and .asp files were created.

    The basic underlying mechanism for rendering the HTML navigation tree from an XML document object, however, has remained relatively unchanged from the original article. Thanks again to the readers for their valuable feedback. Feel free to send more suggestions, requests, or complaints. :-)

  • By Sam M. Reynoso
    Wisebits Digital Solutions, Inc.


  • Download the complete source code (in ZIP format)
  • View a live demo!


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article