Universally Related Popup Menus AJAX Edition: Part 2 / Page 2 | WebReference

Universally Related Popup Menus AJAX Edition: Part 2 / Page 2

[previous] [next]

Universally Related Popup Menus AJAX Edition: Part 2 [con't]

An alert message (line 341) is displayed and the function aborted if any of the lists aren't found. This message is intended for people who are using the script rather than Web clients, since the only thing that can cause the error is probably invalid syntax. Here is the code for the initializeLists() function in its entirety, excluding local functions:

The base list has its own initialization function because it has more responsibility than the child lists. These include holding a pointer to the callServer() function and determining the browser-dependent code to fire the onchange event. Its initialization function is called initBaseList() (lines 87,335).

The first variable assignment in initBaseList() creates the global BASE_LIST reference and stores it using the this pointer (line 131). In fact, most of the functions are called within the scope of the list object so we're always able to reference it. The next line creates a reference to the local callServer() function by creating a property of the same name (line 133). This has to be done because by the time callServer() is called, we no longer have a reference to it as it is private to the initLists() function.

The base list again takes on the this scope for the getFireEventFunction() function (line 89). Here, it plays a key role because the function code is determined based on the syntax that the browser recognizes rather than by using traditional browser sniffing techniques. It's easier to test for this.fireEvent than for the browser type, version, and platform - especially since many modern browsers have been known to spoof Internet Explorer!

The getFireEventFunction() function returns one of two functions based the evaluation of the this.fireEvent statement (line 93) If it's recognized by the browser's JavaScript interpreter, then it's assumed that the browser is Internet Explorer, or at least behaves like it! In this case, the function calls the proprietary IE fireEvent() function with "onchange" as the parameter (line 94). For Level 2 DOM Mozilla type browsers, a bit more code is required. First we have to create an event of the type HTMLEvents (line 97). Other types include UIEvents, MouseEvents, and MutationEvents. Next, we have to specify exactly which kind of event we want to call, using the initEvent() function (line 98). For HTMLEvents, this covers abort, blur, change, error, focus, load, reset', resize, scroll, select, submit, and unload. It takes two additional arguments: whether or not the event bubbles, and whether or not it's cancelable. Finally, we call dispatchEvent() to fire it (line 100).

DOM Level 2 Events

Event Module Event type to pass to createEvent Method to be used to initialize the event
User Interface event module "UIEvents" event.initUIEvent
Mouse event module "MouseEvents" event.initMouseEvent
Mutation event module "MutationEvents" event.initMutationEvent
HTML event module "HTMLEvents" event.initEvent
The Event Object Methods
HTMLEvents and generic Events initEvent( 'type', bubbles, cancelable )
UIEvents initUIEvent( 'type', bubbles, cancelable, windowObject, detail )
MouseEvents initMouseEvent( 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget )
MutationEvents initMutationEvent( 'type', bubbles, cancelable, relatedNode, prevValue, newValue, attrName, attrChange )


In the initializeLists() function above (line 58), we call the bindOnChangeFunctionsToListElement() function to bind the setSublists() function to the list's onchange event by adding it as a listener (line 337). We could just assign it directly by writing "list.onchange = function { setSublist(this.id) };" but this would overwrite any existing functions in the SELECT's tag, such as "<SELECT size=1 onchange='validate(this)'>".

Adding an event listener to an event isn't hard to do, but cross-browser differences adds a bit of unavoidable complexity. In the getFireEventFunction() function (line 89), there are two means to the same end. In Internet Explorer, the attachEvent() function (line 75) is used, while in DOM 2 compliant browsers, you add a listener to an event by calling addEventListener() (line 71). The World Wide Web Consortium had their hands full when deciding on an event model because the two main standards were diametrically opposed, with Netscape using something called the capturing model, and Internet Explorer endorsing bubbling. Basically, the two camps emerged as a result of answering the questing of what should happen if an element within another element both have handlers for the same event: Should the parent (outer) element fire first, or should the child (inner element)? Netscape concluded that the parent should fire first. That's Event Capturing. Internet Explorer took the opposite stance and decided that the child should be the one to fire first! That's event bubbling. The W3C took the middle road in creating their own W3C Event Model whereby the event is first captured until it reaches the innermost element and then bubbles up again. This allows the developer to select either style of event firing by supplying the addEventListener() function with a Boolean argument. I used "constants" to show that true means capture and false means bubble (line 62). In practice, unless you have a specific reason to use capturing, stick with bubbling, as I did here.

Another browser difference is the way that the event is referenced. In Mozilla style browsers, the event is passed to the function as an argument, whereas in IE, it's stored in the window.event property. One way to combine these disparate models is to pass the firing element as an argument, since the source element is the only event property that we use. This is typically done by testing for e.target. If that's a no go, the event's srcElement will reference the element instead (line 66).

The last line of the bindOnChangeHandlerToList() function returns true to the initLists() function so it can proceed (lines 79,337). If the browser fails to recognize either event registration models, an error message is displayed (line 79) and the function exits without returning a value to the initLists() function (line 81). This has the effect of halting execution of the script as the (non-)value of "undefined" propagates back to the initialize() function and evaluates to false (line 15).

Here's an in-depth look at what happens when the callServer() function is called:

The keystone of AJAX in JavaScript are the XMLHttpRequest and XMLHTTP (Microsoft's ActiveX implementation) objects, which provide programming interfaces or API's that enables the performing of HTTP requests. We create the AJAX object using the static getXmlHttpInstance() method of the XmlHttpObjectManager class (line 225). The XmlHttpObjectManager is a static singleton that acts as XmlHttpRequest object factory. The XmlHttpRequest object that is returned is also a singleton because we reuse the same one for all server calls. If we are successful in creating it, we proceed to construct the URL which will be used to call the server-side ASP component (line 227). The list ID is appended as the first argument in the URL's hash (line 229,see sidebar below). The other two arguments are optional (lines 231,233) as the code value does not apply to the base list and the file dsn does not have to be supplied unless it differs from the default one.

[previous] [next]