Ugly Grafted Event Handlers
March 15, 1999
Many client-side scripts revolve around the occurrence of
events, which have been explored at great length
in a previous article,
"Events in JavaScript: An Inside Look".
In brief, events occur on a web page either as a result
of user interaction (clicking a mouse button, moving the
mouse pointer, typing a word) or an internal change-of-state
to the page (completion of loading, leaving the page).
The elements on a page which can trigger events are known
as "targets" or "target elements,"
and we can easily understand how a button which triggers a
Click event is a target element for this event.
Typically, events are defined through the use of event
handlers, which are bits of script that tell the
browser what to do when a particular event occurs at a
particular target. These event handlers are commonly written
as attributes of the target element's HTML tag.
The event handler for a Click event at a form field
button element is quite simple to understand:
<input type="button"
name="click1"
value="Click me for fun!"
onClick="event_handler_code">
The event_handler_code portion of this example is
any valid JavaScript and it will be executed when the
specified event is triggered at this target element. At
first this seems convenient, but let's turn back to our
desire to segregate script from markup and you can see
something ugly going on here: the programming language has
been grafted directly into the HTML tag. This should horrify you.
This practice, although largely necessary in the existing
web development model, leads to several painful problems
(not to mention swelling and lesions). For one, imagine a
large web page with many elements and many event handlers.
Reading, and therefore debugging or modifying, the source
code of this page will be very confusing, as various
bits of script, in the form of event handlers, are strewn
throughout the page grafted into HTML tags here and there.
Team members who may be working on the HTML markup can easily
and inadvertently ruin the behavior of the page
by messing with the event handlers, and this may not even
be their fault. Some designers rely partially on visual
HTML editors, and some of these applications are not smart
enough to leave well enough alone and may strip event
handlers from tags.
There are three solutions to the problem of grafted event
handlers, the first two of which are not even close
to perfect and the third of which, although quite good,
is a proprietary feature only supported in Microsoft Internet
Explorer 5. With that glowing introduction, we now look
at each in turn.
Modular Event Handlers
The lessons we've learned about modular scripts applies
very well to event handlers. Which of these is easier
to read?
Explicit event handler
<input
type="button"
name="click1"
value="Click me!"
onClick="total+=50;
tax=calcTax(total);
document.priceform.total.value=total+tax;">
Modular event handler
<input
type="button"
name="click1"
value="Click me!"
onClick="subTotal();">
To make this example more dramatic, imagine that there
are 10 of these buttons on the page. By constructing
a modular script, wherein the subtotal is calculated
using a common modular function subTotal(), the event
handler code need only call this function rather than
perform the calculations there inside the HTML tag. While
this doesn't eliminate script from being grafted inside
the tag, it does minimize the amount of code we have to
wade through within the tags, easing the problem of
mixing programming language with markup. Of course, this example
assumes that the function subTotal() has been
defined somewhere earlier in the page, such as embedded into
the <HEAD> section or included via an external .js
file.
Assigning Event Handlers
Although it is typical to attach event handlers to target
elements using HTML attributes, it is not the only
way. Most tutorials will rely on the attribute technique
because it is simple and reliable, but there is another,
more obscure method of doing this. You can assign a reference
to an event handler to the target element via the
Document Object Model, using the scripting language.
The Document Object Model (DOM), you may know, exposes each
element of the web page as an object in object oriented
programming parlance, which possess properties that reflect
its various characteristics.
Consider the following bit of HTML which creates a simple
form with one form field, a button.
<form name="forms1">
<input
type="button"
name="click1"
value="Click me!"
>
</form>
In DOM-speak, the document object for this page contains a
form object, named "forms1", and this form
object contains a button object, named "click1".
Using JavaScript syntax you can reference the button
object thusly:
document.form1.click1
Other scripting languages may use a different syntax,
depending on how they access objects and properties. In
any case, the button object click1 possesses a
property for each event handler, such as onclick.
We can assign a function reference to this property,
thereby attaching the event handler to this target element
without meddling inside the HTML tag.
document.form1.click1.onclick=subTotal;
This looks quite simple but there are several important
caveats to note. First, the line of code above would
have to execute at some point after the button has
been created by its HTML tag. Otherwise, the page won't
know that there is an element named "click1".
Second, you'll notice something slightly unusual about the
assignment above: the reference to subTotal
lacks the typical parentheses that are included in a
function call. There is a good reason for this -- in this
technique, subTotal is a reference to the function,
not a function call. If we included the parentheses,
the page would have immediately launched subTotal()
rather than assign it to the event handler for this
element.
This puts us in a bind -- without the ability to include
parentheses, how can you pass parameters to the event
handler function? You can't, since this:
document.form1.click1.onclick=subTotal(total);
won't work. Never fear, there is another clever trick
to step around this muddy puddle. Watch the magic:
document.form1.click1.onclick=function e ()
{ subTotal(total) }
Rather than assigning a function reference to this event
handler, we simply assign an explicit function which
is called when the event occurs. This function, in turn,
executes what we really wanted to launch, in this
case, subTotal(total). Sneaky! If you didn't catch
that the first time through, here is another way of looking
at it: you can think of the following two constructs as
assigning the same event handler "subTotal(total)"
to the button named click1.
Equivalent methods of assigning an event
handler to a target element.
| HTML |
<input type="button" name="click1"
value="Click me for fun!"
onClick="subTotal(total)">
|
| Script |
document.form1.click1.onclick=
function e () {subTotal(total) }
|
This is about the best you can do in the version 4 browsers
as far as avoiding the need to graft script inside
of HTML tags to attach event handlers. Internet Explorer
5 trumps these tricks and workarounds with a new feature
called "behaviors". That's the good news. The
bad news is that only Internet Explorer 5 supports behaviors.
One step forward, one step back.
External Scripts
Creating Portable and Modular Client-Side Scripts
Behaviors in Internet Explorer 5
|