| home / programming / javascript / rg / 1 | [previous] |
|
|
The last line in the function calls the resetLists() function to set the default listIndex if need be. This is only done the first time that the page loads because the saveHistory fix remembers the last index. If we were to call it when returning to the page, it would reset the lists to their initial values. We can tell that it's the first time the page loads by looking for empty lists. We can't check the base list because it is already populated by the XSL transform code, but any child lists would do nicely.
//first time
loading. All the sublists have not been populated yet.
if (lists[1].options.length == 0) resetLists();
Lets continue with the resetLists() function, since it is called from initLists().
The first line of code will abort the function if the lists array has not been initialized – in other words, the browser is not compatible!
if (lists.length == 0) return;
By default we set the base list to the empty item, using the following variable:
var selIndex=0;
If there is a hidden field in the form we retrieve the value. I took the extra precaution of converting the value to an Integer because hidden field values are always strings, which could be anything! The following line is some validation to make certain that we have a valid and useable index to set the base list to:
if (
typeof(lists[0].form.defaultIndex) != "undefined" )
{
var di =
parseInt(lists[0].form.defaultIndex.value);
if (!isNaN(di) && di >= 0 && di <
lists[0].options.length) selIndex = di;
}
Here's where we set the base list's index:
lists[0].selectedIndex = selIndex;
This is another area that you could tweak the code, as you may want the child lists to also have defaults.
We then call the getSubList() function by firing the onChange() event. A bit of testing is required to determine the best way to fire the event:
if(lists[0].fireEvent)
{
lists[0].fireEvent("onchange");
}
else if(lists[0].onchange)
{
lists[0].onchange();
}
That brings us to the getSubList() function. Its purpose is to populate the next child list with the associated contents and remove all the following lists' items, so that there's no remaining garbage from the previous selection. The listbox is passed to the function so that we have a direct reference to its properties. With that we can get the code through the ”value” property, and, most importantly, we can get the child list through our sublist array.
var code =
elt.options[elt.selectedIndex].value;
var text = elt.options[elt.selectedIndex].text;
var subList = lists.sublists[elt.name]; //see how easy that was!
First, we empty all the child's elements.
// null out
options in reverse order (bug work-around)
for (var i = subList.options.length - 1; i >= 0; i--) subList.options[i] =
null;
Throughout the script, I have assigned a value of "-1" to denote an empty selection. You may want to change this to your own code. The important thing here is that we don't want to try to find list items for a blank selection. Assuming that a valid selection was made, we would proceed to retrieve that node, replace the current NodeList with its children, and finally, call the fillList() function to populate the child list. For instance, if we select "HATCHBACK" from the models list, the script will retrieve all the levels for that model:
The XML:
<m2 value="10" text="HATCHBACK">
< <m3 value="27">GT</m3>
< <m3 value="26">SE</m3>
</m2>
The JavaScript:
if (code !=
"-1")
{
var xslQuery;
var NodeList;
var i=0;
if (ie)
{
xslQuery = '//*[@value="' + code + '" and @desc="' +
text + '"]';
NodeList = xml.selectNodes(xslQuery);
}
else
{
NodeList = xml.getElementsByTagName(elt.id);
while ( i < NodeList.length
&& NodeList[i] .getAttribute("value") !=
code
&& NodeList[i].getAttribute("desc") !=
text ) {i++}
}
fillList(subList, NodeList.item(i).childNodes);
}
This is another area where browser compatibility became an issue. Internet Explorer has an excellent function called selectNodes. It accepts an XSL query to retrieve any node(s) you want, based on the criteria you supply. Other browsers provide no such functionality, thus leaving us no choice but to resort to iterating through every node in search of the one we want, using the list’s ID property.
The following code removes all the items from the remaining child lists. The first thing we do is get the sublist of the sublist by using its name property! Since it is quite possible that there are no more sublists, we check to see that it is not "undefined," using the typeof() function. The loop will continue to execute as long as there are sublists:
//null out all
sublists options
subList = lists.sublists[subList.name];
while (
typeof(subList) != "undefined" )
{
for (var i =
subList.options.length - 1; i >= 0; i--) subList.options[i] = null;
subList = lists.sublists[subList.name];
}
Our last function to look at is fillList(). As the name implies, it populates a listbox with options. We have to check whether the text is stored as an attribute or as straight text (the value is always an attribute):
<m1 value="2" text="ALFA ROMEO">
vs.
<m3 value="27">GT</m3>
Once we have the text, we can proceed to
create the option for that item:
for (var i = 0; i < NodeList.length; i++)
{
text = NodeList.item(i).getAttribute("desc");
if (text == null || text.length == 0)
text = NodeList.item(i).childNodes.item(0).nodeValue;
listBox.options[listBox.options.length] = new Option(text,
NodeList.item(i).getAttribute("value"));
}
Here is a working example of the URPM's in action.
[Ed. Note: These scripts are kindly hosted by our sister site, ASP101.com]
Overall, the script works quite well in the browsers that I tested, but there are still some unresolved quirks. One of the most perplexing was that Firefox forgets the list selections when navigating between pages. If you select items form the lists, go to another page, and come back, they will revert to their defaults. This behavior can be altered through code, but it requires so much new functionality that it would warrant a separate script. In an effort to keep the script simple, I left it as is. There are many other improvements and customizations that could be made to the scripts. For instance, the new XMLHttpRequest Object is looking like a viable means of fetching data from the server for every onChange event, instead of downloading all the data with the page. You may also want to code your own XML parser, implement some error handling, or maybe even accommodate multiple selections. My hope is that this version of the URPMs will serve as a basis for your own unique creations. As such, it should not be taken as the definitive solution but rather as a starting point for meeting your own individual requirements.
My ASP Solution: Single Form Version II
Synchronous vs. Asynchronous Processing Explained
Robert Gravelle is the guitar player for the Canadian Classic Metal band http://www.ivoryknight.com/ as well as a developer for the Canadian Border Services Agency. His band's latest album "Unconscience," available through http://www.cdbaby.com/cd/ivoryknight2, was mixed and mastered by Jeff Waters of Annihilator fame and features artwork by acclaimed Hungarian artist Gyula Havancsak. You can reach Rob at blackjacques@musician.org.
| home / programming / javascript / rg / 1 | [previous] |
Created: March 27, 2003
Revised: June 2, 2005
URL: http://webreference.com/programming/javascript/rg/1