The JavaScript Apostle
Dueling Event Models: A Cross-Platform Look
By Danny Goodman
Send comments
and questions about this article to View Source.
Click here for printer-friendly version
The last time I wrote about events here in View Source (Getting
Ready for JavaScript 1.2 Events), it was in anticipation of the upgraded
event model in Netscape Communicator. That model has been realized (with
some new items that weren't in the prerelease version available at the
time), and scripts do a nice job with events when the page is intended
for a Communicator-only audience. But if visitors to your site include
Internet Explorer 4 (IE4) users, you've discovered that some of the advanced
features of Communicator's event model don't work the same way in IE4.
As I'll show in this article, it is possible to take advantage of both
browsers' event models in the same document. This will become more important
to you over time as you add scripted Dynamic HTML (DHTML) designs to your
pages.
BASIC EVENTS
Events are the lifeblood of scripting and interactivity in a document.
They're the initiators, the links between the user and the document. The
number and granularity of events available to JavaScript increased substantially
in Communicator. With one exception (the DragDrop event, whose utility
in Communicator requires signed scripts), all of the events in Communicator
are supported in IE4. Table 1 shows the events supported in both browsers
and the objects from both browsers that support them. Each browser has
additional event or object support that is not cross-platform, but as the
table proves, the basics are well covered where the two browsers meet.
Table 1. Events supported in Communicator and IE4
| Event |
Objects |
| abort |
image |
| blur |
window, text, textarea, password, select |
| change |
text, textarea, select |
| click |
link, area, button, radio, checkbox, reset, submit |
| dblclick |
link |
| error |
window, image |
| focus |
window, text, textarea, password, select |
| keydown |
text, textarea, password |
| keypress |
text, textarea, password |
| keyup |
text, textarea, password |
| load |
window, image |
| mousedown |
link, button, radio, checkbox, reset, submit |
| mouseout |
link, area |
| mouseover |
link, area |
| mouseup |
link, button, radio, checkbox, reset, submit |
| move |
window |
| reset |
form |
| resize |
window |
| select |
text, textarea, password |
| submit |
form |
| unload |
window |
One event not listed in Table 1 is the mousemove event. This important
DHTML event is supported in both browsers but in different ways, as I discuss
later in this article.
ASSIGNING EVENT HANDLERS
TO OBJECTS
Probably the most common way of assigning a script action to an event is
via the HTML attribute associated with that event. Event attributes combine
the word "on" with the event name (for example, onClick). You
can assign an in-line script inside the tag, as in
<INPUT TYPE="button" NAME="myButton" VALUE="Click Here" onClick="alert('Howdy')">
or invoke a separate script function defined elsewhere in the document:
<INPUT TYPE="button" NAME="myButton" VALUE="Click Here" onClick="handleClick()">
For all of the events and objects listed in Table 1, you can add the
associated event attribute to the object's tags and expect identical behavior
in both browsers.
A second way of assigning event handler functions to objects first became
available in Navigator 3 and is available in both Communicator and IE4.
The same event handlers you know from HTML tag assignment can also be set
as properties of the object. To be compatible with both browsers, make
sure the name of the event handler in these assignment statements is the
all-lowercase version of the HTML attribute, as in
document.forms[0].myButton.onclick = handleClick
The right side of this kind of assignment statement contains a reference
to the function to be invoked when the event fires. Such references omit
the parentheses normally associated with functions. One other caveat about
this type of event handler assignment: the assignment statement must appear
in the document after the function and the object have been defined.
Otherwise a script error pops up as the document loads.
If you've looked at DHTML code written exclusively for IE4, you may
have seen one additional way that this browser links a script to an object
and event. In IE4, a <SCRIPT> tag can be earmarked for a specific
event of a specific object by way of extra tag attributes that Communicator
ignores. The tag set contains only statements that are to run when the
IE4 object's event fires (that is, there is no function definition inside
that tag set). You cannot use this assignment technique in a cross-platform
page, however, because Communicator treats the tag like any other <SCRIPT>
tag, and runs the statements as the document loads. Destination: Script
Error City.
EVENT PROPAGATION
If you've been comforted so far by how much the two browsers have in common,
please take a seat and fasten your seatbelt. The ride gets a little bumpy
from here on.
Not every page must concern itself with event propagation, but for interactive
content that features a large number of objects sharing scripted behaviors,
it can be much more convenient to dissect and operate on all related events
at an object higher up the document object model hierarchy. Instead of
assigning similar or identical event handlers to a dozen objects, one event
handler in, say, the document object takes care of the whole thing.
For efficient handoff of an event to another object, the browser must
have a mechanism that allows an event to traverse the hierarchy. Both Communicator
and IE4 have such mechanisms. Unfortunately for cross-platform development,
each mechanism is the antithesis of the other: Communicator events trickle
down the hierarchy; IE4 events bubble up the hierarchy.
Trickle-Down Events
Communicator's event-trickling mechanism is described more fully in Getting
Ready for JavaScript 1.2 Events, but I'll hit the highlights here to
help you understand the differences between Communicator and IE4 event
propagation.
When a user clicks a button viewed in Communicator, the browser initially
sends that event to the window object. From there it goes to the document
object. Lastly, it reaches the target of the click, where an onClick
event handler can process the event. But if you want the event to
be processed or preprocessed in the window or document level, you must
turn on event capture for either object (or both). Window and document
objects in Communicator have a captureEvents() method that lets
you specify precisely which event types the objects should grab as they
pass through. Event types are properties of the Event (capital
"E") object. Therefore, for the document object to capture the click event
intended for a document's button, you tell the document to capture all
click events:
document.captureEvents(Event.CLICK)
You must also assign an onClick event handler to the document
object:
document.onclick = handleClick
With these statements in place in the loaded document, when a user clicks
on any object that is capable of reacting to a click event, the handleClick()
function is invoked. It is up to the statements inside the handleClick()
function to examine more details about the event (as described later
in Event Objects). This function can do its work
and simply gobble up the event, or it can route the event to its intended
target (or another target, for that matter).
To summarize Communicator's propagation mechanism:
-
Events start at the window level and automatically go to the intended target
object unless the window or document object is explicitly instructed to
capture that event type.
-
A capturing window or document level must have an event handler assigned
to that object for that object type.
-
Whether the event ultimately reaches its intended target is up to the event
handler assigned to the window or document object that captures that event.
Bubble-Up Events
Internet Explorer 4's propagation mechanism initially directs an object's
event to the intended target. The event invokes an event handler if one
is defined for that object. Under normal conditions, after that event handler
function does its thing, the event then continues up the containment hierarchy.
I must point out that there is a distinction between the document object
hierarchy you're accustomed to in Communicator and IE4's containment hierarchy.
The latter is based on HTML elements that are containers (that is, that
have start and end tags). For example, consider the following skeletal
document:
<HTML>
<BODY>
<FORM>
<DIV>
<INPUT TYPE="button">
</DIV>
</FORM>
</BODY>
</HTML>
In IE4, virtually every container element can have an onClick event
handler assigned to it. Therefore, if a user clicks the button, the click
event traverses the container hierarchy, passing through <DIV>,
<FORM>, and <BODY> in that order. If you want the document-level
object to handle all click events from several related buttons, you can
place a single onClick event handler in the <BODY> tag
and omit onClick event handlers for the buttons and all intervening
objects.
To prevent an event from bubbling up, you must cancel event bubbling
for the current event. This gets into the event (lowercase "e") object
discussion coming up, but here is one way you can keep a click event held
within a button object:
<INPUT TYPE="button" ... onClick="handleClick(); window.event.cancelBubble=true">
You can also include the cancelBubble statement inside the
function invoked by the event handler. It affects only the current event
being handled.
To summarize IE4's propagation mechanism:
-
An event starts at its intended target and invokes an event handler if
one is defined.
-
Unless explicitly instructed to cancel, the event bubbles up the containment
hierarchy after the event handler of the target (if an event handler is
defined) executes its last statement.
-
Events bubble all the way to the top of the containment hierarchy, unless
canceled along the way.
WORKING TOGETHER
As opposite as these two systems appear, they can work quite well together
if your intention is to write event handlers high up the object hierarchy
that are to be shared among several related objects. Only a tiny bit of
platform-specific branching is needed, because both browsers share a lot
of the right syntax.
Example 1 shows a very simple document that handles all click events
at the document level. Thus, all clicks on either the document or the button
ultimately invoke the sayHey() function. The code at the beginning
of the script is a generic routine that sets platform-flag global variables
for use throughout the document. The sayHey() function is the
function that is to be invoked by a click of the mouse button. Called by
the onLoad event handler, the init() function turns on
event capture for Communicator users, causing all click events to be directed
to whatever function is referenced by the document object's onClick
event handler, defined in the subsequent statement. Both browsers
have the document object's onclick event handler set to call the
sayHey() function. When this is run in IE4, the click event from the
button bubbles up to the document object, where the event is handled.
Example 1
<HTML>
<HEAD>
<TITLE>New Window</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var isNav4, isIE4
if (parseInt(navigator.appVersion.charAt(0)) >= 4) {
isNav4 = (navigator.appName == "Netscape") ? true : false
isIE4 = (navigator.appName.indexOf("Microsoft" != -1)) ? true : false
}
function sayHey() {
alert("Hey!")
}
function init() {
if (isNav4) {
document.captureEvents(Event.CLICK)
}
document.onclick = sayHey
}
</SCRIPT>
</HEAD>
<BODY onLoad="init()">
<FORM>
<INPUT TYPE="button" NAME="myButton" VALUE="Click Here">
</FORM>
</BODY>
</HTML>
Mousemove events in Communicator are not affiliated with any object. You're
expected to capture this event at the window or document level, and assign
a function to the object's onmousemove property. Just like the
event handler assignment in the init() function of Example 1,
setting the onmousemove property of a document object will be
recognized by both browsers.
Example 1 obviously lacks an important feature: the ability to distinguish
between a click on a button and a click on the document background. That's
where event objects -- and their entirely different treatments in the two
browsers -- come into play.
EVENT OBJECTS
When an event occurs in either Communicator or Internet Explorer 4 -- whether
the event is the result of a user action or a system action -- the browser
generates an event (lowercase "e") object that contains myriad details
about the event. For mouse events, for example, this object knows where
on the page the event occurred, the object under the cursor at that instant,
and which modifier keys, if any, were held down at that moment. It is not
only a shared script that may need to know this information about an event.
For example, if you want to guard against the entry of numbers into a text
field as the user types, the script must be able to inspect each typed
character and determine whether the typed character is an allowable one.
This is precisely the kind of information that the event object carries
with it.
Communicator's event object is automatically passed to functions invoked
by event handlers that had been assigned as event properties. Therefore,
with the following kind of event handler definition
document.onclick = handleClick
the function in Communicator can catch the event object as a parameter
variable:
function handleClick(evt) {
// statements
}
For event handlers defined as tag attributes, the event keyword
can be passed in much the same way as you pass the this keyword:
<INPUT TYPE="button" ... onClick="handleClick(event)">
Inside the function, the properties of the event object can be examined
to help the function decide how to react to the event.
IE4's event object is a different animal. It is treated as a property
of the window object. In other words, there is always an event object hanging
around in the window. When a user or system action fires an actual event,
the event object picks up the properties of that event and holds onto them
until the event handler processing that event finishes its job. At idle
time, the event object intentionally loses everything it knew about the
most recent event. During processing, scripts can examine the properties
of this window.event object for the kind of event-specific details
described earlier.
EVENT OBJECT PROPERTIES
Confounding cross-platform development a bit more is that the browsers
share precious few event object property names. This means that unless
you devise your own set of APIs to bridge the gap (as shown for CSS-Positioning
tasks in CSS-Positioning
-- The Dynamic HTML Neutral Zone), you'll have some additional platform-specific
branching in your main document scripts to handle the different property
names and reference syntax.
Table 2 shows the primary event object properties for each browser --
at least the ones whose functionality is identical in both browsers. Each
browser has additional, platform-specific properties. Items in common are
shown in boldface.
Table 2. Communicator and IE4 event object properties
| Communicator |
Property description |
Internet Explorer 4 |
| Property |
Values |
Values |
Property |
| modifiers |
Event
object
properties |
Modifier keys pressed when the event occurred |
Boolean |
altKey
ctrlKey
shiftKey |
| pageX |
pixel count |
Horizontal coordinate of event in content region of browser window |
pixel count |
clientX |
| pageY |
pixel count |
Vertical coordinate of event in content region of browser window |
pixel count |
clientY |
| screenX |
pixel count |
Horizontal coordinate of event relative to entire screen |
pixel count |
screenX |
| screenY |
pixel count |
Vertical coordinate of event relative to entire screen |
pixel count |
screenY |
| target |
object |
Object that is to receive, or that fired, the event |
object |
srcElement |
| type |
event name |
String value of event name (e.g., "click", "mousedown", "keypress") |
event name |
type |
| which |
integer |
Mouse button or keyboard key code (but some code values differ with
browser) |
integer |
button
keyCode |
The coordinate properties are pretty straightforward, so I'll devote
two examples to presenting little laboratories you can use to experiment
with the event object under different browser circumstances. While both
examples use the common tag-based event handler declarations, there is
no reason you cannot apply what you'll see in the examples into pages that
use the cross-platform event propagation described earlier. The exact implementation
depends heavily on the structure of your documents and the events you design
into them.
DETECTING MODIFIER KEYS
The first event object laboratory, shown in Example 2, presents a standard
link and text input field. Both the onMouseDown event handler
of the link and the onKeyPress event handler of the text field
invoke the same function -- a poor man's cross-platform event propagation
scheme if I ever saw one. The function branches to look at the platform-specific
event object and determine which of the four possible modifier keys are
pressed when the event occurs. There is an extra lesson in this example,
as you'll soon see. If you're using a level 4 browser, you can view
Example 2.
Example 2
<HTML>
<HEAD>
<TITLE>Modifiers Keys Properties</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var isNav4, isIE4
if (parseInt(navigator.appVersion.charAt(0)) >= 4) {
isNav4 = (navigator.appName == "Netscape") ? true : false
isIE4 = (navigator.appName.indexOf("Microsoft" != -1)) ? true : false
}
function checkMods(evt) {
var form = document.forms[0]
if (isNav4) {
form.modifier[0].checked = evt.modifiers
& Event.ALT_MASK
form.modifier[1].checked = evt.modifiers
& Event.CONTROL_MASK
form.modifier[2].checked = evt.modifiers
& Event.SHIFT_MASK
form.modifier[3].checked = evt.modifiers
& Event.META_MASK
} else if (isIE4) {
form.modifier[0].checked = window.event.altKey
form.modifier[1].checked = window.event.ctrlKey
form.modifier[2].checked = window.event.shiftKey
form.modifier[3].checked = false
}
return false
}
</SCRIPT>
</HEAD>
<BODY>
<B>Event Modifier Keys</B>
<HR>
<P>Hold one or more modifier keys and click on
<A HREF="javascript:void(0)" onMouseDown="return checkMods(event)">
this link</A> to see which keys you are holding.</P>
<FORM NAME="output">
<P>Enter some text with uppercase and lowercase letters:
<INPUT TYPE="text" SIZE=40 onKeyPress="checkMods(event)"></P>
<P>
<INPUT TYPE="checkbox" NAME="modifier">Alt
<INPUT TYPE="checkbox" NAME="modifier">Control
<INPUT TYPE="checkbox" NAME="modifier">Shift
<INPUT TYPE="checkbox" NAME="modifier">Meta
</P>
</FORM>
</BODY>
</HTML>
Both calls to the checkMods() function pass the Communicator
event object. The function defines a parameter variable for the incoming
object, and that variable is used in the branch for Communicator processing;
it is ignored for IE4 processing. That's because IE4 references to the
event object look to the window object for its event property.
Each browser also has a very different way of determining when a modifier
key has been pressed in concert with an event.
-
In Communicator, the event object's modifiers property is compared
(with a bitwise AND operator) against the constants held by the Event
(capital "E") object (an object that works here a lot like the
Math object). The Event object has a separate constant value
for each of the four modifier keys. If you AND the
constant against the modifiers property, you get true
if the constant value is a component of the modifiers value.
-
For IE4, the event object has separate properties for each of three modifier
keys (there is no property for the meta key). Each of these properties
is a simple Boolean value, which allows the lab to set the checked
property of the checkbox objects based on which modifier key or keys
were held down during the event.
When you use this example on either browser, you'll experience some unexpected
behavior in the text field. The browser does not yield its sovereignty
over numerous Ctrl+key combinations or any Alt+key combinations. This may
cause you to narrow your sights for defining accelerator key combinations
for your pages.
BUTTON AND KEY CODES
The final example of this survey shows you how to extract information about
which mouse button or which keyboard key is involved in an event. Example
3 lists another laboratory page for experimenting with these features.
Results of the event object properties for this information are shown in
the status bar as you click a button or type into a textarea. Like the
lesson about modifier keys and text fields in Example 2, this lab holds
an extra lesson. But first, let's look at the code.
Example 3
<HTML>
<HEAD>
<TITLE>Button and Key Codes</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var isNav4, isIE4
if (parseInt(navigator.appVersion.charAt(0)) >= 4) {
isNav4 = (navigator.appName == "Netscape") ? true : false
isIE4 = (navigator.appName.indexOf("Microsoft" != -1)) ? true : false
}
function checkWhich(evt) {
var theKey
if (isNav4) {
theKey = evt.which
} else if (isIE4) {
if (window.event.srcElement.type == "textarea") {
theKey = window.event.keyCode
} else if (window.event.srcElement.type == "button") {
theKey = window.event.button
}
}
status = theKey
return false
}
</SCRIPT>
</HEAD>
<BODY>
<B>Button and Key Codes From Event Objects</B> (results in the status bar)
<HR>
<FORM NAME="output">
<P>Click on this
<INPUT TYPE="button" VALUE="Button" onClick="checkWhich(event)">
with either mouse button (if you have more than one).</P>
<P>Enter some text with uppercase and lowercase letters:<BR>
<TEXTAREA COLS=40 ROWS=4 onKeyPress="checkWhich(event)" WRAP="virtual"></TEXTAREA></P>
</FORM>
</BODY>
</HTML>
I cheat a bit again for the purposes of the demonstration by calling one
function from both the onClick event handler of the button and
the onKeyPress event handler of the textarea. The function that
handles the event must branch to accommodate the different ways each browser
extracts button and key information.
-
For Communicator, the which property of the event object is an
integer whose range of values depends on the event type. For a button,
the which value is 1 for the primary button; for a keyboard key,
the which value is the ASCII value of the character associated
with the key. Therefore, the script extracts one value regardless of the
event or object type.
-
For IE4 processing, the script examines the kind of object that originally
received the event (window.event.srcElement). The type property
of relevant objects reveals the kind of object receiving the user action.
For a textarea, the script needs to read the keyCode property
of the event object. For standard English characters, the values are the
same as Communicator's which property. To get the mouse button
used to click the screen button, the script reads the window.event.button
property. While this value is an integer as it is in Communicator,
the values are different: a click of the primary mouse button is a value
of 0.
If you're using a level 4 browser, you can view
Example 3.
The bonus lesson in this example is that even though the event objects
are supposed to report different values for the right mouse button (if
you have a multiple-button mouse), in practice the browsers don't let you
trap for this user event. Right clicks in Windows 95 or NT, for example,
display a context-sensitive pop-up menu, without passing the event to the
page.
MOMENTOUS EVENTS
When I first learned of the different event models in the two browsers,
I wondered how they could possibly be reconciled for cross-platform development.
To my delight, I discovered that there was significant overlap in the implementations.
And even where the approaches are opposite of each other, it isn't too
difficult to devise scripts that accommodate many features of both models
in one document, since the respective syntaxes scarcely step on each other.
View Source wants your feedback!
Write to us and let
us know
what you think of this article.
Author and consultant Danny
Goodman's twenty-fifth book is JavaScript
Bible, published by IDG Books. He is currently pounding the keyboard
on a new book about Dynamic HTML.
(11.97)
- Related Reading
Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use