Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning JavaScript With DOM Scripting And Ajax - From Novice To Professional (2006)

.pdf
Скачиваний:
83
Добавлен:
17.08.2013
Размер:
17.27 Mб
Скачать

156

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

If you use this tool function to add your function to the page, you will not override the other functions that should be invoked by the page’s load event. It was and is a big step in the right direction.

The preceding methods work—to my knowledge—for all browsers that support JavaScript out there. In particular, if you have to support MSIE 5 on a Mac, this is as far as you can go. Newer browsers support an improved event model that clearly distinguishes between different aspects of event handling.

Events in the W3C-Compliant World

Note The examples in this section need to be executed in a DOM-2–compliant browser. I recommend Mozilla Firefox version 1.5. MSIE 6 is not DOM-2 compliant, and the examples will not work in it. This part of the chapter explains how events were defined and meant to work by the W3C. Afterward, we’ll extend these methods to work with MSIE and other noncompliant browsers.

The W3C DOM-2 and the upcoming DOM-3 specifications approach the event handling issue a bit differently. First of all, they define different parts of the event’s occurrence up to the use of the retrieved data in detail:

The event is what happens, for example, click.

The event handler, for example onclick, is in DOM-1 the location where the event gets recorded.

In DOM-2, this is slightly different—you deal with event target and event listener:

The event target is where the event occurred, in most cases an HTML element.

The event listener is a function that deals with that event.

DOM-3 also brings the concept of event capturing, which has no real browser support yet. If you really want to understand these, check the W3C specifications (http:// www.w3.org/TR/DOM-Level-3-Events/events.html); I won’t cover event capturing here, as it is not part of usable JavaScript yet.

Applying Events

You apply an event via the addEventListener() method. This function takes three parameters: the event as a string and without the “on” prefix, the name of the event listener function (without parentheses), and a Boolean value called useCapture, which defines whether event capturing should be used or not. For now, it is safe to set useCapture to false.

If you wanted to apply the function infoWindow() to the link via addEventListener(), you’d use the following:

var triggerLink=document.getElementById('info'); triggerLink.addEventListener( 'click', infoWindow, false);

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

157

If you wanted to add a hover effect by calling the function highlight() when the mouse is over the link and the function unhighlight() when the mouse leaves the link, you can add a couple more lines:

var triggerLink=document.getElementById('info'); triggerLink.addEventListener( 'click', infoWindow, false); triggerLink.addEventListener( 'mouseout', highlight, false); triggerLink.addEventListener( 'mouseover', unhighlight, false);

Checking Which Event Was Triggered Where and How

It may seem that in terms of ease of developing, you are back to square one: you once again have to find the element to read the href from inside infoWindow(). This is true; however, by using addEventListener, you prompt standards-compliant browsers to give you the event object, which you can read out via a parameter called e.

You might have seen this e before and wondered what it was and whether you should trust an e without knowing where it came from. It is very confusing at first to simply use a parameter without sending it when you apply the event, but once you learn about the event object, you’ll never go back to using onevent properties. The event object has many attributes you can use in the event listener function:

target: The element that fired the event.

type: The event that was fired (for example, click).

button: The mouse button that was pressed: 0 for left, 1 for middle, and 2 for right.

keyCode/data/charCode: The character code of the key that was pressed. The W3C specifications use data; however, it is not widely supported. Most browsers use charCode instead, and keyCode is MSIE’s implementation. The good news is that all modern browsers understand keyCode, which means you can use that for now until data is more widely supported in user agents.

shiftKey/ctrlKey/altKey: A Boolean—true if the Shift, Ctrl, or Alt key (respectively) was pressed.

The full list of what is available is dependent on the event you are listening to. You can find the whole list of attributes in the DOM-2 specification: http://www.w3.org/TR/DOM-Level-2-Events/ events.html#Events-Registration-interfaces.

Using the Event object, you can easily use one function to deal with several events:

var triggerLink=document.getElementById('info'); triggerLink.addEventListener( 'click', infoWindow, false); triggerLink.addEventListener( 'mouseout', infoWindow, false); triggerLink.addEventListener( 'mouseover', infoWindow, false);

158

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

You can use the same function for all three events and check the event type:

function infoWindow(e){ switch(e.type){

case 'click':

//Code to deal with the user clicking the link

break;

case 'mouseover':

//Code to deal with the user hovering over the link

break;

case 'mouseout':

//Code to deal with the user leaving the link

break;

}

You could also check the element the event occurred at by checking its nodeName. Notice that you once again have to use toLowerCase() to avoid cross-browser problems:

function infoWindow(e){ targetElement=e.target.nodeName.toLowerCase(); switch(targetElement){

case 'input':

//Code to deal with input elements

break; case 'a':

//Code to deal with links

break; case 'h1':

// Code to deal with the main heading break;

}

}

Stopping Event Propagation

Assigning events and intercepting them with event listeners also means that you need to take care of two problems: one is that a lot of events have default actions—click for example might make the browser follow a link or submit a form, whereas keyup might add a character to a form field.

The other one is called event bubbling, and it basically means that when an event occurs at an element, it also occurs at all the parent elements of the initial element.

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

159

Event Bubbling

Let’s go back to the HTML markup for the news list:

exampleEventBubble.html

<ul id="news"> <li>

<h3><a href="news.php?item=1">News Title 1</a></h3> <p>Description 1</p>

<p class="more"><a href="news.php?item=1">more link 1</a></p> </li>

<!-- and so on --> </ul>

If you now assign a mouseover event to the links in the list, hovering over them will also trigger any event listener that might be on the paragraphs, the list items, the list, and all the other elements above that in the node tree right up to the document body. As an example, you’ll see how to attach event listeners to each of them that point to appropriate functions:

eventBubble.js

bubbleTest={

init:function(){

if(!document.getElementById || !document.createTextNode){return;} bubbleTest.n=document.getElementById('news'); if(!bubbleTest.n){return;}

bubbleTest.addMyListeners('click',bubbleTest.liTest,'li');

bubbleTest.addMyListeners('click',bubbleTest.aTest,'a');

bubbleTest.addMyListeners('click',bubbleTest.pTest,'p');

},

addMyListeners:function(eventName,functionName,elements){ var temp=bubbleTest.n.getElementsByTagName(elements); for(var i=0;i<temp.length;i++){

temp[i].addEventListener(eventName,functionName,false);

}

},

liTest:function(e){ alert('li was clicked');

},

pTest:function(e){ alert('p was clicked');

},

160

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

aTest:function (e){ alert('a was clicked');

}

}

window.addEventListener('load',bubbleTest.init,false);

Now all the list items will trigger the liTest() method when they are clicked, all the paragraphs will trigger the pTest() method, and all the links will trigger the aTest() method.

However, if you click the paragraph, you will get two alerts:

p was clicked li was clicked

You can prevent this by using the e.stopPropagation() method, which will ensure that only the event listener applied to the links will get the event. If you change the pTest() method to the following:

stopPropagation.js—used in exampleStopPropagation.html

pTest:function(e){ alert('p was clicked'); e.stopPropagation();

},

the output will be

p was clicked

Event bubbling is not really that problematic, as you are not often likely to assign different listeners to embedded elements instead of to their parents. However, if you want to learn more about event bubbling and the order of what happens when an event occurs, Peter-Paul Koch has written an excellent explanation, available at http://www.quirksmode.org/js/events_order.html.

Preventing Default Actions

The other problem you might have is that events on certain elements have default actions. Links, for example, load another document. You might not want that to happen, and you have to stop the default action after you’ve executed your event listener function.

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

161

In the DOM-1 event handler model, you did this by returning a false value in the function that was called:

element.onclick=function(){ // Do other code

return false;

}

If you click any of the links in the earlier example, they load the linked document. You can override this by using the DOM-2 preventDefault() method. Let’s test it by adding it to the aTest method:

preventDefault.js—used in examplePreventDefault.html

aTest:function (e){ alert('a was clicked'); e.stopPropagation(); e.preventDefault();

}

Clicking the links now just causes the alert to show up:

a was clicked

The links, on the other hand, are not being followed, and you’ll stay on the same page to do something different with the link data.

For example, you could initially show only the headlines and expand the content when they are clicked. First, you need some more classes in the style sheet to allow for these changes.

listItemCollapse.css (excerpt)

.hide{

display:none;

}

li.current{

background:#ccf;

}

li.current h3{ background:#69c;

}

162

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

The script for collapsing the elements is not complex, but uses all the event handling elements we talked about:

newsItemCollapse.js

newshl={

// CSS classes

overClass:'over', // Rollover effect hideClass:'hide', // Hide things currentClass:'current', // Open item

init:function(){ var ps,i,hl;

if(!document.getElementById || !document.createTextNode){return;} var newsList=document.getElementById('news'); if(!newsList){return;}

var newsItems=newsList.getElementsByTagName('li'); for(i=0;i<newsItems.length;i++){

hl=newsItems[i].getElementsByTagName('a')[0];

hl.addEventListener('click',newshl.toggleNews,false);

hl.addEventListener('mouseover',newshl.hover,false);

hl.addEventListener('mouseout',newshl.hover,false);

}

var ps=newsList.getElementsByTagName('p'); for(i=0;i<ps.length;i++){

DOMhelp.cssjs('add',ps[i],newshl.hideClass);

}

},

toggleNews:function(e){

var section=e.target.parentNode.parentNode;

var first=section.getElementsByTagName('p')[0];

var action=DOMhelp.cssjs('check',first,newshl.hideClass)?'remove':'add'; var sectionAction=action=='remove'?'add':'remove';

var ps=section.getElementsByTagName('p'); for(var i=0;i<ps.length;i++){

DOMhelp.cssjs(action,ps[i],newshl.hideClass);

}

DOMhelp.cssjs(sectionAction,section,newshl.currentClass);

e.preventDefault();

e.stopPropagation();

},

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

163

hover:function(e){

var hl=e.target.parentNode.parentNode;

var action=e.type=='mouseout'?'remove':'add'; DOMhelp.cssjs(action,hl,newshl.overClass);

}

}

window.addEventListener ('load',newshl.init,false);

The results are clickable news headings that show associated news excerpts when you click them. The “more” links stay unaffected and will send the visitor to the full news article when clicked (see Figure 5-6).

Figure 5-6. Expanding new items by clicking the headings

Let’s go through the whole script step by step. After defining the CSS class properties and checking for the necessary elements, you start looping through the list items, grab the first link (which is the one inside the heading), and assign event listeners for click, mouseover, and mouseout. The click event should fire the newshl.toggleNews() method, while both mouseout and mouseover should trigger newshl.hover().

newsItemCollapse.js (excerpt)

for(i=0;i<newsItems.length;i++){

hl=newsItems[i].getElementsByTagName('a')[0];

hl.addEventListener('click',newshl.toggleNews,false);

hl.addEventListener('mouseover',newshl.hover,false);

hl.addEventListener('mouseout',newshl.hover,false);

}

164

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

You hide all paragraphs inside the list item by applying the hiding class to them:

newsItemCollapse.js (excerpt)

var ps=newsList.getElementsByTagName('p'); for(i=0;i<ps.length;i++){

DOMhelp.cssjs('add',ps[i],newshl.hideClass);

}

The toggleNews() method grabs the current section by reading out the target of the event object. The target is the link, which means that if you want to reach the list item, you need to go up to the next parent node twice.

newsItemCollapse.js (excerpt)

toggleNews:function(e){

var section=e.target.parentNode.parentNode;

You read the first paragraph of the list item and check whether it already has the hiding class assigned to it. If that is the case, define the variable action as remove; otherwise define it as add. Set another variable called sectionAction and define it as the opposite of action with the same options.

newsItemCollapse.js (excerpt)

var first=section.getElementsByTagName('p')[0];

var action=DOMhelp.cssjs('check',first,newshl.hideClass)?'remove':'add'; var sectionAction=action=='remove'?'add':'remove';

Loop through all the paragraphs and either remove the hiding class or add it, depending on the action. Do the same for the section and the current class, but this time use sectionAction. This effectively toggles the visibility of the paragraphs and the styling of the heading.

newsItemCollapse.js (excerpt)

var ps=section.getElementsByTagName('p'); for(var i=0;i<ps.length;i++){

DOMhelp.cssjs(action,ps[i],newshl.hideClass);

}

DOMhelp.cssjs(sectionAction,section,newshl.currentClass);

Stop the link that was initially clicked from being followed by calling preventDefault() and disallow event bubbling by calling stopPropagation().

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

165

newsItemCollapse.js (excerpt)

e.preventDefault();

e.stopPropagation();

},

The hover method grabs the list item via parentNode and checks the type of the event that was used to call the method. If the event was mouseout, it defines the action as remove, otherwise as add. It then applies or removes the class from the list item.

newsItemCollapse.js (excerpt)

hover:function(e){

var hl=e.target.parentNode.parentNode;

var action=e.type=='mouseout'?'remove':'add'; DOMhelp.cssjs(action,hl,newshl.overClass);

}

And finally, add an event listener to the window object that fires off newshl.init() when the window has finished loading.

newsItemCollapse.js (excerpt)

}

window.addEventListener ('load',newshl.init,false);

Now you know how to make something change once clicked in DOM-2–compliant browsers. It is time to think about the other browsers out there, and make sure those get support, too.

Fixing Events for the Non-W3C-Compliant World

Now that you know the theory of event handling (and in part have support in a lot of new browsers and hopefully those to come), it is time to look at the offenders violating the agreed standard and learn how to deal with them.

Note The helper methods here are already contained inside DOMhelp.js, and you can find them there if you want to use them without DOMhelp.

One of them is MSIE 6, which is actually understandable considering its age and the role it has to play—both browser and operating system file manager. MSIE has its own event model, and equivalents for the standard methods are named differently and sometimes behave differently.