470,565 Members | 1,786 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Share your developer knowledge by writing an article on Bytes.

A Guide to Coding Cross-Browser Scripts Part 2: Event Normalization

kovik
1,044 Expert 1GB
Normalizing Event Triggers in JavaScript

For those of you who are too lazy to keep up with the standards of the current JavaScript versions, this will suffice:

Expand|Select|Wrap|Line Numbers
  1. elem.onmouseup = SomeFunction;
However, that method has a lot of limitations.
  • Only one function per event trigger
  • No control over bubbling/capturing
  • Dynamic addition and removal of events requires creation and deletion of objects
  • It's old

The correct, modern way of handling event triggers are these methods:
  • addEventListener, removeEventListener
  • attachEvent, detachEvent

You see that there are two different ways, right? That's because, yet again, Microsoft decided to impose their own methods on us. In concept, these pairs of functions do the exact same thing, but in reality they don't. Microsoft's way (attachEvent/detachEvent) does not allow you to control whether or not you want to capture events.

The fact that there are two different sets of methods for doing this means that we need to know which ones the browser supports in order to know which ones to use. You could do so with if statements checking whether the browser supports addEventListener or attachEvent, but you'll likely have so many event triggers that it won't be practical. So, I've written two functions do it for you:

Expand|Select|Wrap|Line Numbers
  1. /**
  2. *   Add an event listener to an object
  3. *   @param      object
  4. *   @param      evt         event
  5. *   @param      func            function
  6. *   @param      capture
  7. *   @return     boolean
  8. */
  9. function AddEvent(object, evt, func, capture)
  10. {
  11.     if(typeof func != 'function')
  12.     {
  13.         return false;
  14.     }
  15.     if(object.addEventListener)
  16.     {
  17.         object.addEventListener(evt, func, capture);
  18.         return true;
  19.     }
  20.     else if(object.attachEvent)
  21.     {
  22.         object.attachEvent('on' + evt, func);
  23.         return true;
  24.     }
  25.     return false;
  26. }
  27.  
  28. /**
  29. *   Removes an event listener
  30. *   @param      object
  31. *   @param      evt         event
  32. *   @param      func            function
  33. *   @param      capture
  34. *   @return     boolean
  35. */
  36. function RemoveEvent(object, evt, func, capture)
  37. {
  38.     if(typeof func != 'function')
  39.     {
  40.         return false;
  41.     }
  42.     if(object.removeEventListener)
  43.     {
  44.         object.removeEventListener(evt, func, capture);
  45.         return true;
  46.     }
  47.     else if(object.detachEvent)
  48.     {
  49.         object.detachEvent('on' + evt, func);
  50.         return true;
  51.     }
  52.     return false;
  53. }

Here's how to use them:
Expand|Select|Wrap|Line Numbers
  1. AddEvent(elem, 'mouseup', SomeFunction, false);
  2. RemoveEvent(elem, 'mouseup', SomeFunction, false);
  • The first parameter is an element to add the event listener to
  • The second parameter is the name of the event (without "on")
  • The third parameter is the function (the actual function object with the parentheses)
  • The last parameter is a boolean asking determining whether or not to capture the event

This will simplify your code. The only thing that you have to look out for is that even if you set the last parameter to true, it will still bubble in Internet Explorer instead of capture, so you still have to code for bubbling. Those are the breaks.

These functions will return false if neither of these methods are available, but all modern browsers support at least one of these methods, so it's safe to ignore it.

You should keep both of these events in a global JavaScript file that you use in all of your scripts to simplify event handling.

Original Source: Normalizing Event Triggers in JavaScript


Normalizing Event Objects in JavaScript

Just like almost everything worth using in JavaScript, Microsoft's Event object and the standard Event object are not the same. Because of this, we'll need to normalize the Event object in order to use it.

For those of you who are unfamiliar with JavaScript's Event objects, they are automatically sent to Function objects that are attached to an event trigger as the first parameter, no matter what you do.

The Event object has a lot of different properties, a lot of which are not cross-browser compatible. Differences consist of:
  • layerX/layerY and offsetX/offsetY
  • relatedTarget and fromElement/toElement
  • target and srcElement
  • keyCode and charCode

So, I wrote a function to normalize all of these into this variables (respectively):
  • offsetX/offsetY
  • relatedTarget
  • src
  • key

You can, of course, change the names of the variables in the follow function if you'd like to. This will make it so that you can use event objects without worrying about cross-browser issues.

Expand|Select|Wrap|Line Numbers
  1. /**
  2. *   Gets an event with all needed properties
  3. *   @param      e           event
  4. *   @return     event object
  5. */
  6. function GetEvent(e)
  7. {
  8.     if(!e)
  9.     {
  10.         e               = window.event;
  11.     }
  12.  
  13.     if(e.layerX)
  14.     {
  15.         e.offsetX       = e.layerX;
  16.         e.offsetY       = e.layerY;
  17.     }
  18.  
  19.     if(e.type == 'mouseover' && !e.relatedTarget)
  20.     {
  21.         e.relatedTarget     = e.fromElement;
  22.     }
  23.     else if(e.type == 'mouseout' && !e.relatedTarget)
  24.     {
  25.         e.relatedTarget     = e.toElement;
  26.     }
  27.  
  28.     e.src               = e.srcElement || e.target;
  29.     e.key               = e.keyCode || e.charCode;
  30.  
  31.     return e;
  32. }

Here's how you use it:
Expand|Select|Wrap|Line Numbers
  1. function HandleEvent(e)
  2. {
  3.     // Normalize the event
  4.     e = GetEvent(e);
  5.  
  6.     // Do stuff with the event object
  7. }
And now you can simplify your event handling code by actually changing the event object to be normalized from browser to browser. You should keep this function handy in a file that you include in all of your scripts.

Original Source: Normalizing Event Objects in JavaScript
Jul 2 '07 #1
3 7720
hdanw
61
Why not combine the two?

I have added a table of events so that when the event is fired we can convert event data to "uniform" event format, before the event is fired.

If multiple handlers are attached to a single event for a single object then they are called in the order they were added, and a single instance of the event return data exists for all methods.

Expand|Select|Wrap|Line Numbers
  1. var globalthunkingindex = new Array();
  2.  
  3. function ThunkingHandlerIndexObject(object, evt, func, capture)
  4. {
  5.     this.object = object;
  6.     this.event  = evt;
  7.     this.func = new Array();
  8.     this.func.push(func);
  9.  
  10.     this.capture = capture;
  11. }
  12.  
  13. function FindGlobalThunkingEntry(obj, eventtype)
  14. {
  15.     for( var i = 0; i < globalthunkingindex.length; i++)
  16.     {
  17.         if( obj == globalthunkingindex[i].object)
  18.         {
  19.             // found same object is this event?
  20.             if( eventtype == globalthunkingindex[i].event)
  21.             {
  22.                 return i;
  23.             }
  24.         }
  25.     }
  26.     return -1;
  27. }
  28. function AddHandlerToGlobalThunkingIndex(object, eventtype, func, capture)
  29. {
  30.  // DH added feb 08    
  31.     var index = FindGlobalThunkingEntry(object, eventtype);
  32.     if( index < 0 ) 
  33.     {   // doesn't exist
  34.         globalthunkingindex.push( new ThunkingHandlerIndexObject(object, eventtype, func, capture));
  35.     }
  36.     else
  37.     {
  38.         // does exist, add this function to list 
  39.         globalthunkingindex[index].func.push(func);
  40.     }
  41.  /////////////DH   
  42. }
  43. function RemoveHandlerFromGlobalThunkIndex(obj, eventtype, func)
  44. {
  45.  // DH added feb 08   
  46.     var lindex = FindGlobalThunkingEntry(obj, eventtype);
  47.     if( lindex < 0) return false;
  48.  
  49.     var lobj = globalthunkingindex[lindex];
  50.     if( lobj.func.length > 0) 
  51.     {
  52.         for( var findex = 0; findex < lobj.func.length; findex++)
  53.         {
  54.             if( lobj.func[findex] = func)
  55.             {   
  56.                 // this is our func to remove
  57.                 break;
  58.             }
  59.         }
  60.         lobj.func.splice(findex,1);
  61.     }
  62.     if( lobj.func.length == 0 )
  63.     {
  64.         // list is empty we can remove this node
  65.         globalthunkingindex.splice(lindex,1);
  66.     }
  67. } /////////////DH 
  68.  
  69.  
The previous code was modified to use our event lookup and data normalization.

Expand|Select|Wrap|Line Numbers
  1. /**
  2. *   Add an event listener to an object
  3. *   @param      object
  4. *   @param      evt         event
  5. *   @param      func            function
  6. *   @param      capture
  7. *   @return     boolean
  8. */
  9.  
  10.  
  11. function AddEvent(object, evt, func, capture)
  12. {
  13.     if(typeof func != 'function')
  14.     {
  15.         return false;
  16.     }
  17.  // DH added this line    
  18.     AddHandlerToGlobalThunkingIndex(object, evt, func, capture);
  19.  
  20.     if(object.addEventListener)
  21.     {
  22.         /// DH modified from 
  23.         //  object.addEventListener(evt, func, capture);
  24.         // to 
  25.         object.addEventListener(evt, GlobalThunkHandleEvent, capture);
  26.  
  27.         return true;
  28.     }
  29.     else if(object.attachEvent)
  30.     {
  31.         /// DH modified from 
  32.         //  object.attachEvent('on' + evt, func);
  33.         // to 
  34.         object.attachEvent('on' + evt, GlobalThunkHandleEvent);
  35.  
  36.          return true;
  37.     }
  38.     return false;
  39. }
  40.  
  41. /**
  42. *   Removes an event listener
  43. *   @param      object
  44. *   @param      evt         event
  45. *   @param      func            function
  46. *   @param      capture
  47. *   @return     boolean
  48. */
  49. function RemoveEvent(object, evt, func, capture)
  50. {
  51.     if(typeof func != 'function')
  52.     {
  53.         return false;
  54.     }
  55.  
  56.     if(object.removeEventListener)
  57.     {
  58.         object.removeEventListener(evt, func, capture);
  59.  
  60.       // DH added this line, must occur after event is unattached
  61.         RemoveHandlerFromGlobalThunkIndex( object, evt, func);
  62.  
  63.         return true;
  64.     }
  65.     else if(object.detachEvent)
  66.     {
  67.         object.detachEvent('on' + evt, func);
  68.  
  69.       // DH added this line, must occur after event is unattached
  70.         RemoveHandlerFromGlobalThunkIndex( object, evt, func);
  71.  
  72.         return true;
  73.     }
  74.     return false;
  75. }
  76. /**
  77. *   Gets an event with all needed properties
  78. *   @param      e           event
  79. *   @return     event object
  80. */
  81. function GetEvent(e)
  82. {
  83.     if(!e)
  84.     {
  85.         e               = window.event;
  86.     }
  87.  
  88.     if(e.layerX)
  89.     {
  90.         e.offsetX       = e.layerX;
  91.         e.offsetY       = e.layerY;
  92.     }
  93.  
  94.     if(e.type == 'mouseover' && !e.relatedTarget)
  95.     {
  96.         e.relatedTarget     = e.fromElement;
  97.     }
  98.     else if(e.type == 'mouseout' && !e.relatedTarget)
  99.     {
  100.         e.relatedTarget     = e.toElement;
  101.     }
  102.  
  103.     e.src               = e.srcElement || e.target;
  104.     e.key               = e.keyCode || e.charCode;
  105.  
  106.     return e;
  107. }
  108.  
And finally the code that gets it all done:

Expand|Select|Wrap|Line Numbers
  1. function GlobalThunkHandleEvent(e)
  2. {
  3.     // Normalize the event
  4.     e = GetEvent(e);
  5.     // locate redirection 
  6.     var index = FindGlobalThunkingEntry(e.src, e.type);
  7.     // Do stuff with the event object
  8.     var handlerlist = globalthunkingindex[index].func;
  9.  
  10.     var IsNotMasked = true;
  11.     for( var i =0; i < handlerlist.length; i++)
  12.     {
  13.         if( handlerlist[i](e) == false )
  14.         {
  15.               IsNotMasked = false;
  16.         }
  17.     }// note a single instance of event data exists
  18.      // care must be taken when attempting to mask events
  19.      // 
  20.  
  21. // is this IE only?
  22. // e.returnValue = IsNotMasked;
  23.  
  24.      return IsNotMasked;
  25. }
  26.  
  27.  
This is by no means optimized. It should be elementary to modify the 2 search algorithms to cut down on the time spent looking. Or add a quick hash.

Happy coding.

Dan -
Feb 4 '08 #2
hdanw
61
Why not combine the two?

I[code]
function GlobalThunkHandleEvent(e)
{
// Normalize the event
e = GetEvent(e);
// locate redirection
var index = FindGlobalThunkingEntry(e.src, e.type);
Sorry I haven't tested this on any other browsers. I am using IE.

My concern would be that the "e.type" in function GlobalThunkHandleEvent(e)
may not be as plain as IE? I don't know. Does any KNow if this becomes something like "on"+"mousemove" as apposed to "mousemove"? If so it can be corrected in "GetEvent".

Thanks.

Dan -
Feb 4 '08 #3
acoder
16,027 Expert Mod 8TB
My concern would be that the "e.type" in function GlobalThunkHandleEvent(e)
may not be as plain as IE? I don't know. Does any KNow if this becomes something like "on"+"mousemove" as apposed to "mousemove"? If so it can be corrected in "GetEvent".
It would be "mousemove". See this example.
Feb 7 '08 #4

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

1 post views Thread by Donald Smith | last post: by
4 posts views Thread by Mikkel christensen | last post: by
7 posts views Thread by john_williams_800 | last post: by
10 posts views Thread by ryann18 | last post: by
2 posts views Thread by telsave | last post: by
6 posts views Thread by ampo | last post: by
1 post views Thread by livre | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.