Section F. Targeting


F. Targeting

When an event occurs, you often want to do something with the element on which the event took place. In order to do that, you need to find this element.

For instance, when the user mouses over a link in Dropdown Menus, you need to find out which link that is, and whether it has a submenu (i.e., a nested <ul>). If it does, the time has come to drop the menu down. If the user clicks on a form field in Usable Forms, you need to find this element and see if it has a rel attribute.

There are two important targeting properties. The first is the event object's target or srcElement property, which always refers to the element on which the event took place. The second is the this keyword, which usually (but not always!) refers to the element on which you have defined the event handler.

The difference

Take Dropdown Menu. As we saw in 7D, the script defines onmouseover and onmouseout event handlers on the <ul>, even though the actual events take place on the <a>s that make up the bulk of the menu. Let's tweak the script a bit to alert the nodeNames (see 8C) of the HTML elements the event target and the this keyword refer to:

var x = document.getElementsByTagName('ul'); for (var i=0;i<x.length;i++) {     if (x[i].className != 'menutree') continue;     x[i].onmouseover = navMouseOver; } function navMouseOver(e) {     var evtTarget = [the target];     alert(evtTarget.nodeName);     alert(this.nodeName); } 


The user mouses over an <a> element. The event bubbles up to the <ul>, and navMouseOver() is executed. The event target is the <a> element, since that's the element the user moused over. Therefore the first alert shows 'A'. However, the this keyword refers to the element on which you defined the event handler, and therefore the second alert shows 'UL'.

Which of the two you need depends on the context of your script, but in general it's best to have both references available so that you can use either one if necessary.

this

As we saw in 5J, the this keyword always refers to the object through which a method is invoked, and it must be enclosed in a function body in order to work.

In the traditional and W3C models, JavaScript event handlers are methods of the HTML elements on which they're registered. Therefore, the this keyword refers to that HTML element, and is something you can use in your event handling scripts.

Take a simple script that's supposed to change the background color of an element on which the user clicked:

function test() {    this.style.backgroundColor = '#cc0000'; } 


As we saw in 5J, any function can be seen as a method of JavaScript's global object, the window object. Since the this keyword always refers to the object a function is a method of, it now refers to the window object.

Figure 7.8. Without any more commands, the this keyword in the function test() refers to the window. In the onclick property of the element, it refers to the element.


If we execute test() without any more preparations, the function tries to set the style.backgroundColor of the window. Since the window has no style property, that action generates an error message.

If we use traditional event registration, we take a function and assign it as an object's event handler. It now becomes a method of x.

x.onclick = test; 


Now the this keyword refers to x. If the user clicks on x, the function correctly sets the background color of HTML element x to red.

Figure 7.9. If we assign the function test() to the element's onclick property, the this keyword refers to the element.


Now let's change the registration a bit:

x.onclick = function () {     test(); } 


When the user clicks on x, the event handler calls test(). Now the function is invoked through the global object, and therefore the this keyword refers to the global object: the window. The function misfires once more.

Figure 7.10. If the onclick function calls the function test(), the this keyword refers to the window.


Examples

If you want to use the this keyword, you have to be sure it actually refers to the element on which the event handler is registered.

In the following cases, the this keyword refers to the correct element:

<element onclick="this.style.backgroundColor="#cc0000"> x.onclick = test; x.addEventListener('click',test,false); 


But in the following cases, it doesn't; test() is called as a global function, and this refers to the window object.

<element onclick="test()"> x.onclick = function () {    test(); } x.addEventListener('click', function () {    test(); },false); x.attachEvent('onclick',test); 


Note the Microsoft attachEvent() method in the last list. As we saw in 7C, its lack of support for the this keyword is the most serious problem of the Microsoft model.

Target

Warning

Browser incompatibilities ahead


The target of an event is always the element the event actually took place on, regardless of where the event handler is defined. There's a minor browser difference here: W3C-compliant browsers call it target, while Explorer calls it srcElement. Fortunately, the problem is easy to solve:

function handleEvent(e) {    var evt = e || window.event;    var evtTarget = evt.target || evt.srcElement; } 


First you access the event object, and then try to read out either its target or its srcElement property. One of the two always works, so that evtTarget now contains a reference to the event target and you can start using it.

Unexpected target values

Every once in a while the event target is not what you expect it to be. The most annoying example is a bug in older Safari versions, where the target of a mouse event is not the element it took place on, but rather the text node contained by that element.

<a href="somewhere.html" >Text</a> document.getElementById('test').onmouseover = handleEvent; function handleEvent(e) {     var evt = e || window.event;     var evtTarget = evt.target || evt.srcElement;     alert(evtTarget.nodeName); } 


When the user mouses over 'Text', most browsers take its parentthe linkas the event target. Older Safari versions, however, take the text node 'Text' itself as the event target, and that can cause weird problems.

The solution is to check if the target is a text node, and if it is, to move one element upward:

function handleEvent(e) {    var evt = e || window.event;    var evtTarget = evt.target || evt.srcElement;    if (evtTarget.nodeType == 3)           evtTarget = evtTarget.parentNode;    alert(evtTarget.nodeName); } 


Now the target is the link in all browsers. (We'll discuss nodeType, parentNode, and text nodes in 8C, 8B, and 8H, respectively.)

In some situations, you encounter more subtle problems, especially when you use events like mouseover and mouseout, which can potentially fire a lot of times. Take Dropdown Menu. As you have seen, it works on an HTML <ul>/<li>/<a> structure and uses event bubbling to deliver the event to the centralized handler on the <ul>.

CSS-wise, a dropdown menu is usually quite complicated. While creating the CSS, you'll frequently move borders and paddings and such from the <li>s to the <a>s and back again, until your styles work in all browsers.

In my version of Dropdown Menu, all <a> elements are tightly fitted together. Therefore, when the user mouses over them, four mouseover events take place, one for every <a> element.

Figure 7.11. No CSS space between the <a> tags. When the mouse moves over the four links, four mouseover events on <a> elements take place.


However, as soon as I add a little bit of CSS padding to the <li>s, the situation changes dramatically.

#nav2 li {     padding: 1px; } 


The <li>s now have a visible area, and the same mouse movement now causes twelve mouseover events: one on every <a> and two on every <li> (padding-top and padding-bottom).

Figure 7.12. As soon as you define li {padding: 1px} in CSS, the <li> elements are visible, and mouseover events also register on them.


If you silently expect all events to take place on <a> elements, you're in for a big shock when you add the padding. That's why I'm so careful in reading out the event target in navMouseOver():

[Dropdown Menu, lines 27-31]

var evt = e || window.event; var evtTarget = evt.target || evt.srcElement; if (evtTarget.nodeName == 'UL') return; while (evtTarget.nodeName != 'LI')     evtTarget = evtTarget.parentNode; 


If the event takes place on a <ul> (i.e., on the <ul > itself), the script doesn't handle it at all. In all other cases it takes the event target and moves up in the document tree until it encounters an <li> element. (Incidentally, this also solves the Safari bug we noted above.) Now the script can work from the assumption that evtTarget is an <li> element.

More generally, if you use trigger-happy events like mouseover and mouseout, be sure to check and double-check the event target before you start using it. It may not be what you expect it to be.

this or target?

When do you use the this keyword and when do you use the event target? There are a few general rules, but there's also a lot of overlap, especially when you have registered the event on the same element that will be the event target.

  • In general, the this keyword is useful when you register the same event handler on a lot of elements and/or when you want to call the event handlers directly, i.e., without an event taking place.

  • In general, the event target is useful when you rely on bubbling to take the event upward in the document tree.

this example

Take Textarea Maxlength. The script registers event handlers on all textareas. It uses the traditional model, so this works as expected:

[Textarea Maxlength, lines 16-17]

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


When the user changes the text in the textarea, checkMaxLength() is called. This function needs to know which textarea the user is currently editingand it finds it by using the this keyword:

[Textarea Maxlength; lines 22-30]

function checkMaxLength() {     var maxLength = this.getAttribute('maxlength');     var currentLength = this.value.length;     if (currentLength > maxLength)             this.relatedElement.className = 'toomuch';     else             this.relatedElement.className = '';     this.relatedElement.firstChild.nodeValue = currentLength; } 


The script now accesses the correct textarea.

But isn't the textarea the user is currently editing also the target of the event? Yes, it is. Then why doesn't the function use the target? Because of the textareas[i].onkeyup() statement in the initialization routine.

That statement essentially calls checkMaxLength() as a method of the textarea object, and in order for the function to work, it must use the this keyword. If it didn'tif it used the event targetthe function call would give error messages, since there's no event object available.

target example

In Usable Forms, the script registers a general onclick event handler on the document, and waits for click events on form fields to bubble up. In addition, the script registers the same function as the onchange event handler of select boxes.

[Usable Forms, lines 21-23 and 53]

var selects = document.getElementsByTagName('select'); for (var i=0;i< selects.length;i++)     addEventSimple(selects[i],'change',showHideFields); // more initialization addEventSimple(document,'click',showHideFields); 


Obviously, when a user clicks on something, the first item on the agenda is to find out which element he clicked on, and whether the action should trigger document changes. Because I use addEventSimple(), the this keyword is useless: it doesn't work in Explorer. Besides, even if it did, it would refer to the document, since that's where I registered the onclick event handler.

In this situation, I use the event's target:

[Usable Forms, lines 70-81, condensed]

var evt = e || window.event; var evtTarget = evt.target || evt.srcElement; if (!(    (evtTarget.nodeName == 'SELECT' && e.type == 'change')    ||    (evtTarget.nodeName == 'INPUT' && evtTarget.getAttribute('rel')) )) return; 


Now the script checks if the target is a <select> and the event type is change; or if the target is an <input> that has a rel attribute. If neither is the case, the function ends; no changes in the document structure are necessary.

Overlap

There are a few cases in which this and the target overlap each other, with few clear-cut advantages on either side. For instance, in Sandwich Picker I set a lot of click events on the Order buttons in the following way (the Trash button works similarly):

[Sandwich Picker, lines 49-50]

var extraLink2 = orderLink.cloneNode(true); extraLink2.onclick = moveToOrderTable; 


In order to find the link the user clicked on, I use the this keyword:

[Sandwich Picker, lines 145-148, condensed]

function moveToOrderTable() {     var node = this.parentNode.parentNode;     document.getElementById('ordered').appendChild(node); 


I could also have registered a general click event on the entire document and used the event's target property for targeting. That script would look something like this:

document.onclick = moveToOrderTable; function moveToOrderTable() {     var evt = e || window.event;     var evtTarget = evt.target || evt.srcElement;     var node = evtTarget.parentNode.parentNode; } 


There's a slight problem with this approach. The Order links need one function, and the Trash links need another one. I could have read the text in the event's target element and gone on from there:

function moveToOrderTable() {        var evt = e || window.event;        var evtTarget = evt.target || evt.srcElement;    var linkText = evtTarget.firstChild.nodeValue;    if (linkText == 'Order') {            // move to order table    }    else if (linkText == 'Trash'); {            // move to start table    } } 


However, I decided that both buttons should have their own event-handling function, because that's clearer overall.

Nonetheless, this example shows that there is no firm, fixed line between cases where the this keyword is preferable and where the event target is better suited. You'll have to develop your own habits within the welter of restrictions we just discussed.



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