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.
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!