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.
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:
- No field present.
- No value assigned.
- 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
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.