Section C. Event-handler registration


C. Event-handler registration

Once you've decided which events to use, you need to tell the browser which function you want to run when that event takes place: "If the user clicks on this element, then run that function." For this, you want to register an event handler.

There are no less than four ways of registering an event handler. The oldest one works in all browsers but violates the separation of structure and behavior. The second one works in all browsers and is an excellent choice in many simple situations. The two remaining ways don't work in all browsers, but have a few important advantages over the other two.

Inline event handlers

The oldest way of registering event handlers is by adding an attribute to an HTML element:

<a href="somewhere.html" onclick="highlightNavItem()"> 


When the user clicks on the link, the highlightNavItem() function runs.

There's an HTML attribute for every event: onmouseover, onsubmit, ondblclick, and so on.

Camelcase

In the past it was customary to write these attributes in camelCase: onClick instead of onclick. HTML is case insensitive, so in HTML pages this doesn't matter. XHTML, though, requires lowercase attribute names, so an onClick attribute does not pass validation. Making the attribute name lowercase solves this problem, and JavaScript doesn't care.


As we discussed in 2C, inline event handlers violate the separation of structure and behavior, and should not be used. Therefore, this book will not treat them in detail. If you encounter them, you should convert them to one of the other event-registration models.

The traditional model

The traditional event-registration model is part of the de facto Netscape 3 standard, and all browsers support it.

Just as with <img src>, <a href>, or other important HTML attributes, JavaScript allows you to access all event-handling attributes as properties of the object that represents the HTML element. Let's port the inline event handler from the previous example to the traditional model:

<a href="somewhere.html" > var x = document.getElementById('somewhere'); x.onclick = highlightNavItem; // note: no parentheses 


x is the object that represents the HTML element, and we set its onclick property to the function highlightNavItem() (onclick now becomes a method of the object x).

No parentheses ()

When registering an event handler in this way, you do not use parentheses (). As we discussed in 5I, the () operator executes a function, and that's not what we want here. Suppose we did this:

   var x = document.getElementById('somewhere');  x.onclick = highlightNavItem(); 


This code gives the command "Execute highlightNavItem() and assign its return value to the onclick property of x."

That's not what we want. We want to execute the function only when the event actually takes place. We want to say "If the user clicks on this element, execute highlightNavItem()."

In order to do that, we must assign the value of the variable highlightNavItem (which is the function) to the onclick property. Now onclick refers to the function highlightNavItem(), and it's executed whenever the user clicks on the HTML element.

x.onclick = highlightNavItem; // note: no parentheses 


We'll discuss this important matter further in 7F.

Executing event handlers directly

As we'll also see in 7F, event handlers are methods of the object they're registered on, and therefore you have the option of executing them directly instead of waiting for the event to take place.

I use this trick in Textarea Maxlength:

[Textarea Maxlength, lines 16-17]

x[i].onkeyup = x[i].onchange = checkMaxLength; x[i].onkeyup(); 


The checkMaxLength() function should be executed when a keyup or a change event occurs on the textarea. However, the textarea may already contain text at the moment the page is loaded. The length of this text should immediately be shown next to the textarea, and therefore checkMaxLength() must be executed at once, without waiting for the event to take place.

Edit Style Sheet does exactly the same thing for the same reasons:

[Edit Style Sheet, lines 33-37]

var els = document.forms[1].elements; for (var i=0;i<els.length;i++) {     els[i].onchange = assignStyles;     els[i].onchange(); } 


The form fields may already have a value, and this value should be processed immediately. Therefore the script fires the onchange event handler immediately after the handler has been registered.

Couldn't I have executed checkMaxLength() and assignStyles() directly, without the detour through the event handlers? No. Both functions use the this keyword, and as we'll see in 7F, that keyword can misfire dramatically if it's used outside its proper context. We have to make sure the functions operate as methods of the correct HTML elements.

Anonymous functions

The examples we've discussed until now all use a normally defined function and copy it to the correct event handler. It's also possible to use anonymous functions, and I sometimes do that with onload event handlers:

window.onload = function () {     // initialize } 


I directly assign a function to window.onload. This is the same as using:

window.onload = init; function init() {     // initialize } 


The only difference between these two cases is that the second one defines the initialization function as a separate function init, while the first doesn't. In both cases, the event handler is assigned a function to be executed when the event takes place, and that's what matters.

Drawbacks of the traditional model

Although the traditional event-handler registration model is easy to use and works in all browsers, you should be aware of its drawbacks. The most important one is that, since you define the value of a method, any subsequent definition overwrites the earlier value.

For instance, in the code below, you first set onclick to the value of doThis, and later you set it to the value of doThat. As a result, only the function doThat() is executed when the user clicks on the element.

   x.onclick = doThis;  x.onclick = doThat; 


If you want to execute both functions when the user clicks on the element, you have to do something like this:

x.onclick = function () {     doThis();     doThat(); } 


Apart from being a bit kludgy, this syntax also causes the this keyword in doThis() and doThat() to refer to the wrong object, because the functions are not methods of the object x.

This problem can become even worse, as Site Survey shows. We already saw that that script needs an onunload event handler on the window and an onclick event handler on the document. But Site Survey may be included in any site, and the host site may already contain an onunload event handler. Therefore I may not do this:

   window.onunload = ST_exit;  document.onclick = ST_openPopup; 


Now if I assign event handlers, I might overrule event handlers of the host site, or the host site's event handlers might overrule mine. The result would be that either the scripts of the host site or Site Survey would not be executed, and that's clearly undesirable.

There are two ways to solve this problem. The first is to check if the site already has a window.onunload and a document.onclick. For instance:

var existingClick = document.onclick || function () {}; document.onclick = function () {     existingClick();     ST_openPopup(); } 


First I create a variable existingClick that contains either the existing document.onclick or, if none is defined, an empty function. Then I define an anonymous function that executes existingClick() and ST_openPopup(), and assign it to document.onclick. This works, but it's a bit of a kludge.

The second, and in my eyes superior, solution is to use the two advanced event-handler registration models.

W3C and Microsoft models

Both W3C and Microsoft have defined advanced event-handler registration models that effectively solve the overwrite problem. These models allow you to define as many event handlers as you like for the same event on the same element.

Let's repeat our doThis/doThat example in the advanced models. First W3C:

x.addEventListener('click',doThis,false); x.addEventListener('click',doThat,false); 


Now doThis() and doThat() are both executed when the user clicks on element x. addEventListener() means "add an extra event handler on this element in addition to any event handler that may already exist." Note, however, that you cannot be sure they will be executed in this order: the browser may execute doThat() first.

As you see, addEventListener() takes three arguments:

  • The event name as a string, without the "on".

  • The function to be executed (without parentheses (), of course; we don't want to execute it right now, but only when the event takes place).

  • A boolean that states whether the event bubbles up (false) or is captured (true). We'll discuss this in 7D, but for now, know that you nearly always use bubbling, and that the third argument is therefore always false.

The Microsoft model works in a similar way:

x.attachEvent('onclick',doThis); x.attachEvent('onclick',doThat); 


Here, too, doThis() and doThat() are both executed when the user clicks on element x. As you see, attachEvent() takes two arguments:

  • The event name as a string, with the "on".

  • The function to be executed (without parentheses (), for the usual reasons).

Adding One Event Handler Multiple Times

In general, you can't add one event handler more than once to an argument. Take this code:

x.addEventListener('click',doThis,false); x.addEventListener('click',doThis,false); 


If the user clicks on the element, doThis is executed only once, not twice.

The exception is in Explorer, where the following code causes the function to be executed twice after one click:

x.attachEvent('onclick',doThis); x.attachEvent('onclick',doThis); 



Removing event handlers

Both advanced models contain methods for removing event handlers. Let's look at removeEventListener() and detachEvent():

x.removeEventListener('click',doThis,false); // W3C x.detachEvent('onclick',doThis); // Microsoft 


Now the doThis() event handler is removed from element x, and only doThat() is executed when the user clicks on the element.

As you see, removeEventListener() takes the same arguments as addEventListener(), and detachEvent() takes the same arguments as attachEvent().

Disadvantage

The advanced models have one disadvantage: it's impossible to find out which event handlers have been registered on an element. Among other things, that means you can't say "Now remove all onclick event handlers from element x."

In the traditional model, this is possible:

x.onclick = null; 


However, the advanced models require you to name the event-handling function you want to remove. If you can't do that because you're not sure which event handlers are currently registered, you can't remove the event handler.

addEventSimple()

Unfortunately, neither of these advanced event-registration models are cross-browser. At the time of writing, Explorer supports only the Microsoft model, while Mozilla and Safari support only the W3C model. Opera supports both.

As always when different browsers support different models, we need a bit of object detection. I generally use the two helper functions addEventSimple() and removeEventSimple() to do this job for me:

[Usable Forms, lines 151-163. These functions are also used in Site Survey, Dropdown Menu and Edit Style Sheet.]

function addEventSimple(obj,evt,fn) {    if (obj.addEventListener)           obj.addEventListener(evt,fn,false);    else if (obj.attachEvent)           obj.attachEvent('on'+evt,fn); } function removeEventSimple(obj,evt,fn) {    if (obj.removeEventListener)           obj.removeEventListener(evt,fn,false);    else if (obj.detachEvent)           obj.detachEvent('on'+evt,fn); } 


Both functions expect three arguments: the object the event should be added to, the event name as a string without 'on,' and the function to be executed. This is a call from Usable Forms:

[Usable Forms, line 53]

addEventSimple(document,'click',showHideFields); 


Differences between W3C and Microsoft models

Until now we've pretended that the W3C and Microsoft models are essentially the same, with the names of the methods to be called as the only difference. Unfortunately, that is not quite true:

  • The Microsoft model does not support event capturing. Since you rarely use capturing anyway, this is not a big problem.

  • The Microsoft model treats the event-handling function as a global function, not a method of the HTML element it's registered on. That means that the this keyword refers to the window instead of to the object the event handler is registered on. This is a serious problem that we'll discuss in more detail in 7F.

The best way

With all that said, what is the best way of registering event handlers? In the example scripts, you'll see that I use either the traditional model or my addEventSimple() function. I prefer the traditional model because it's so simple and completely cross-browser, and because the this keyword always works correctly. There are situations in which I cannot use this model, though, and in those cases I use addEventSimple().

I use the traditional model when I have complete control over all scripts in a page. Sandwich Picker, for instance, was written specifically for my client's site, and since I created the entire site I could control all event-handler registrations. Since there were no other scripts involved, it was safe to use the traditional model.

On the other hand, Usable Forms and Site Survey can be added to any page. These modules should not set event handlers through the traditional model, since those might overwrite or be overwritten by handlers of the native scripts on the host page.

Therefore I use addEventSimple() in Usable Forms and Site Survey. Using the two advanced models ensures that event handlers from my modules and from native scripts don't interfere with each other.

Another situation in which addEventSimple() may be a good choice is when you need mass initialization. For instance, Dropdown Menu and Edit Style Sheets run on the same page, but both have a separate initialization function that should be executed onload. To make sure that the two onload event handlers don't interfere with each other, I set both through addEventSimple().

So in general I use addEventSimple() when several scripts that should not interfere with each other are present (or could be present) on the same page.

Nonetheless, let's repeat it one more time: the this keyword doesn't work correctly when you use addEventSimple(). I usually solve this problem by tiptoeing around it: if my functions don't use this, then there is no problem.

If you need this and cannot use the traditional model because of interference danger, the time has come to look into more complex event-registration functions. I recommend Dean Edwards' solution, which you can find at http://dean.edwards.name/weblog/2005/10/add-event/.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net