"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>