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