Universally Related Popup Menus AJAX Edition: Part 2 | WebReference

Universally Related Popup Menus AJAX Edition: Part 2

By Rob Gravelle


Digg This Add to del.icio.us

Last week we looked at a brief overview of Ajax, relevant JavaScript 1.3 enhancements, how to run the example and using the script within your own Web page. This week we look at additional functionality that you'd like to add to scripts and an in-depth explanation of the JavaScript code.

Customizing the Script

For those of you who like to tinker with things or have additional functionality that you'd like to add to the scripts, here's a line by line explanation of the JS and ASP scripts to help get you better acquainted with the code.

At the very top of the URPM_AJAX.js file there is a couple of global "constants". I could have used real constants by going with JavaScript 1.5, but I felt that 1.3 had more browser support at this time. Hence, these are actually just regular variables. Constants can be distinguished from regular variables by their uppercase names. The DEFAULT_URL stores the name of the default server-side script. If you wish to substitute your own script, remember to use a hidden field in your page, as outlined in part 2. You should never have to change this value. We need to detect Safari browsers because there is a bug in version 3 (at least on Windows 2000).

The initialize() function is the first to be called.  It is called from the BODY tag's onLoad() event with the IDs of each URPM, starting with the base list:

The first call within the initialize() function (see line 5 in box below) is to setOptionalProperties() (line 7). It sets some global constants from hidden fields in the HTML document. However, it is within the initLists() function that most of the work takes place (line 15). Some of its duties include binding the onchange event to our script and setting up the AJAX XmlHttpRequest object. The initLists() function returns true if it's safe to proceed and populate the lists and false if any errors are encountered. Although initLists() runs every time the page loads, the lists are only populated on the first page load because the list options and selections should be retained between page refreshes. If we repopulate the lists every time the page loads, we would wipe out the previous selections! The test to decide whether or not to populate the lists is the presence of options (line 15). In other words, once they're there, we don't load them again. The call to callServer() (line 15) loads the base list and causes a cascade effect to all the child lists. Here is the code for the initialize() function:

Lets take a closer look at the setOptionalProperties() function (line 18) . The optional properties, like all variables, have to be initialized every time as the values are lost between page reloads. Global variables have been prefaced with the window namespace to explicitly show that all global variables are appended as a property to the window Object when created. It's also a useful way to keep track of them. Here's the code for the setOptionalProperties() function:

The getHiddenFieldValue() (line 20) function above is used to retrieve the optional properties from the hidden form fields. There are a few scenarios that it has to deal with, including:

  1. No field present.
  2. No value assigned.
  3. Data type conversion required.

Dealing with all these possibilities is a lot easier than might first appear. Since all form control values are read in as strings, simply testing for the field is enough. There's no need to check for a blank value since that could be valid. A value of false is returned if the field isn't there because it's the most unambiguous value to test for in an if statement. The function is stored in a local variable to show that it's private:

In the case of the BLANK_ENTRY variable (line 46), a bit of conversion is necessary to get the proper value in order to convert "true" and "false" strings to their respective boolean equivalents. It seems straightforward, but it's not as trivial a matter as it first appears. The challenge is the Boolean() function (lines 36 ,40) converts any non-empty string to true! One way to get around this inconvenience is to use the eval() function (line 34). It will attempt to interpret any string as JavaScript code. In doing so, it will also throw errors for any bad syntax that it encounters. Once the string has been evaluated, the Boolean() function must still be used because numbers such as 0 or 1 will be stored as integers. If eval() doesn't recognize the string as JavaScript code, we call the Boolean() function directly so that any non-empty string besides "false" will evaluate to true.

The Boolean function is used to convert a non-Boolean value to a Boolean value. Creating a new Boolean object will also work, but instanciating a new object for a one time use is somewhat wasteful.

x = Boolean(expression) //preferred
x = new Boolean(expression).valueOf() //don't use

Note: If the Boolean object has no initial value or if it is 0, -0, null, "", false, undefined, or NaN, the variable is set to false. Otherwise it is true (even with the string "false")!

Here's the code for the convertToBoolean() function:

A Doubly Linked List
A Doubly Linked List

The next function, initiLists() (line 58), is where all of the URPMs' setup is done. It loops through each ID passed to the arguments array, and attempts to get a reference to each element using the Document Object Model's (DOM) getElementById() function. This is the preferred way to reference form elements nowadays. Previously, you had to use document.formname.elementname notation. This was inconvenient because you had to keep track of the form object as well as the element! The getElementById() (line 22) function deals with this issue by ignoring the form altogether.

The last variable (line 324) stores a reference to the list form element that we initialized on the previous run through the loop. Hence, the base list won't have anything in the last variable. The next property (line 332) is added to the last variable and set to the current list so that each list points to the next one, similar to linked list:

The first time through the loop, we call the initBaseList() function (line 335) using the JavaScript 1.3 call() method (line 335), passing the list as the object, with no arguments. It returns the browser-dependent function code to fire the onchange event programmatically, which is then stored in the fireOnChangeEvent (lines 324, 335) variable.

The bindOnChangeHandlerToList() function (line 337) is called for all except the last list. As the name suggests, it binds our changeHandler function to the list's onchange event by adding it as a listener. Once the function is bound, the fireOnChangeEvent() function will indirectly cause the setSublist() function (line 337) to be called within the scope of the list via the onchange event. We don't bind the setSublist() function (line 337) to the last child list, because it has no sublist to set.