"Script Junkie: Generalized Event Handling in JavaScript" by Alexander Hildyard Web Techniques, January 1999 Web Techniques grants permission to use these listings (and code) for private or commercial use provided that credit to Web Techniques and the author is maintained within the comments of the source. For questions, contact editors@web-techniques.com. [LISTING ONE] <HTML> <HEAD> <TITLE>Netscape Event Handling Test</TITLE> </HEAD> <BODY> <script> document.captureEvents( Event.CLICK ); document.onclick = bodyEvent; function bodyEvent( e ) { alert( "Event " + e.type + " caught at body level" ); if ( e.target.name == "myButton" ) routeEvent( e ); } function buttonEvent( e ) { // Event ends here ... alert( "Event " + e.type + " caught at button level" ); } </script> <form name="myForm"> <input name="myButton" type="button" value="Click Me" onclick="buttonEvent( event )"> </form> </BODY> </HTML> [LISTING TWO] <HTML> <HEAD> <TITLE>Internet Explorer Event Handling Test</TITLE> </HEAD> <BODY> <script> document.onclick = bodyEvent; function bodyEvent() { // Event ends here ... alert( "Event " + window.event.type + " caught at body level" ); } function buttonEvent( e ) { alert( "Event " + e.type + " caught at button level" ); } </script> <form name="myForm"> <input name="myButton" type="button" value="Click Me" onclick="buttonEvent( event )"> </form> </BODY> </HTML> [LISTING THREE] // events.js // Assign event handler object var eventHandler = new eventHandlerArray( null ); // Decide what browser is running var fNetscape = navigator.appName.indexOf( "Microsoft" ) != -1 ? false : true; // Create cross-browser compatible event structure var xEvent = new xEventObject(); function xEventObject() { this.event = null; this.element = null; return this; } function eventHandlerArray() { return new Array( eventHandlerItem( null ) ); } function eventHandlerItem( handlerID ) { this.funcList = new Array() this.hID = handlerID return this } function hookEvent() { var theEvent = hookEvent.arguments[0] var theHook = hookEvent.arguments[1] // Add event capturing at document level if ( fNetscape == true ) { if ( document.captureEvents ) { eval( "document.captureEvents( Event." + theEvent.toUpperCase() + " );" ); eval( "document.on" + theEvent + " = runEvent;" ); } else return; } else { // Field event to document if it hasn't already been fielded if ( !eval( "document.on" + theEvent ) ) eval( "document.on" + theEvent + " = runEvent;" ); } // Locate the event in the event handlers list for ( i = 0; eventHandler[ i ]; i ++ ) { if ( eventHandler[ i ].hID == theEvent ) { // Located the event; find a free slot for this handler for ( j = 0; eventHandler[ i ].funcList[ j ] != null; j ++ ) {} eventHandler[ i ].funcList[ j ] = theHook; return; } } // Event has not yet been assigned a handler, so extend event structure eventHandler[ i ] = new eventHandlerItem( null ); eventHandler[ i ].hID = theEvent eventHandler[ i ].funcList[0] = theHook } function runEvent( event ) { if ( fNetscape == false ) { xEvent.event = window.event; xEvent.element = window.event.srcElement; } else { xEvent.event = event; xEvent.element = event.target; } // Run the event handler list for ( i = 0; eventHandler[ i ]; i ++ ) { if ( eventHandler[ i ].hID == xEvent.event.type ) { // Found an array of handlers for this event; run each in turn for ( j = 0; eventHandler[ i ].funcList[ j ]; j ++ ) { retCode = eval( eventHandler[ i ].funcList[ j ] + "( xEvent )" ) if ( retCode == false ) { // Stop handling event, and cancel default handling too event.cancelBubble = true; return false; } } // Allow default handling, after we have run our events return true; } } // Won't get here return false } [LISTING FOUR] // hStatusWrite.js; // usage: <A HREF="./" NAME="my status message">Hover over me</a> <!-- StatusWrite handler hooks --> hookEvent( "mouseover", "statusWrite" ) hookEvent( "mouseout", "statusCancel" ) hookEvent( "click", "changeName" ) function statusWrite( e ) { if ( e.element.name ) { // Mouse entered anchor: display message window.status = "Element: " + e.element.name } else { // Mouse left anchor: remove message window.status = "Unknown element" } return true; } function statusCancel( e ) { window.status = ""; return true; } function changeName( e ) { if ( e.element.name ) { var oldName = e.element.name; fHighlightNow = false; if ( oldName.indexOf( "mutable" ) == -1 ) return true; var newName = "mutable "; if ( oldName.indexOf( "highlight " ) == -1 && Math.random() * 2 < 1 ) { newName += "highlight "; fHighlightNow = true; } else unhighlightState( e ); if ( oldName.indexOf( "flip " ) == -1 && Math.random() * 2 < 1 ) newName += "flip "; // Assign new name, and display it in status bar e.element.name = newName; statusWrite( e ); // Show highlights immediately if ( fHighlightNow ) highlightState( e ); } return true; } [LISTING FIVE] // hFlipButton.js; // usage: <INPUT TYPE="BUTTON" ...> <!-- FlipButton handler hooks --> hookEvent( "click", "flipState" ) hookEvent( "mouseover", "highlightState" ) hookEvent( "mouseout", "unhighlightState" ) // Store the element we are highlighting _hiElement = null; _timer = null; function flipState( e ) { if ( e.element.name ) { if ( e.element.name.toLowerCase().indexOf( "flip" ) != -1 ) { // Preserve highlighting state, while changing caption var str = e.element.value; if ( str.indexOf( "SLOW" ) != -1 ) e.element.value = str.charAt( 0 ) == "*" ? "*FAST*" : " FAST "; else e.element.value = str.charAt( 0 ) == "*" ? "*SLOW*" : " SLOW "; } } return true; } function highlightState( e ) { if ( e.element.name ) { if ( e.element.name.toLowerCase().indexOf( "highlight" ) != -1 ) { // Set the element to highlight _hiElement = e.element; // Clear previous timers, and apply highlight if ( _timer ) clearTimeout( _timer ); doHighlight(); } } return true; } function unhighlightState( e ) { if ( e.element.name ) { if ( e.element.name.toLowerCase().indexOf( "highlight" ) != -1 ) { // Restore the button to its unhighlit state if ( _hiElement != null ) { str = _hiElement.value; if ( str.charAt( 0 ) == "*" ) { _hiElement.value = " " + str.substring ( 1, str.length - 1 ) + " "; } } // And cancel the highlit element _hiElement = null; } } return true; } function doHighlight() { if ( _hiElement != null ) { var str = _hiElement.value; // Toggle the ordinal and terminal characters between " " and "*" if ( str.charAt( 0 ) == "*" ) _hiElement.value = " " + str.substring ( 1, str.length - 1 ) + " "; else _hiElement.value = "*" + str.substring ( 1, str.length - 1 ) + "*" // Set the repeat rate _timer = setTimeout( "doHighlight()", 300 ); } } [LISTING SIX] <HTML> <HEAD> <TITLE>Generalized Event Handling Test</TITLE> <SCRIPT src = "events.js"></SCRIPT> <SCRIPT src = "hStatusWrite.js"></SCRIPT> <SCRIPT src = "hFlipButton.js"></SCRIPT> </HEAD> <BODY> <!-- This will work in IE, since anchors can have names, but not in NS --> <a href="./someLink.htm" name="link element">A link</a> <form> <!-- IE needs a non-proportional font for these button labels ... --> <br><br> <input type="button" style="font-family:Courier" name="button1" value="BUTTON"> <br><br> <input type="button" style="font-family:Courier" name="flip" value=" SLOW "> <br><br> <input type="button" style="font-family:Courier" name="highlight" value=" ALERT "> <br><br> <input type="button" style="font-family:Courier" name="flip_highlight" value=" FAST "> <br><br> <input type="button" style="font-family:Courier" name="mutable" value=" CHANGE "> <br><br> </form> </BODY> </HTML>