8/23/13
I can still remember the day that I discovered the Mouseover and Mouseout events and how they could be utilized to change images when the user hovered the mousepointer over them. I knew at that moment that my world would never be the same. Later, I came to know of other useful events, such as onload, onsubmit, and onclick. Eventually, I was even hooking up my own custom events using non-obtrusive JavaScript. It wasn’t as simple as registering for an event in the HTML tag, but there was only two event registration models: Internet Explorer’s and everyone else’s! There are a lot more native event types available nowadays, some of which are vendor-specific or only partially supported. Non-universally supported events include Internet Explorer’s mouseenter and mouseleave, as well as Opera’s contextmenu, onbeforepaste, onbeforecut, and afterupdate events, which are recognized by IE and WebKit, but not in Mozilla-based browsers. In those cases, it helps to be able to tell which browsers supports a given event. Ideally, the detection would not rely on browser sniffing, which hardly ever works. We’ll be looking at a few such techniques today.
Detecting Document-level Events
At its core, checking for an event is not unlike checking for supported properties. The idea is to use the in keyword to test the event name against the node’s attribute collection. This test returns a true or false value accordingly:
alert('onclick' in document.documentElement); //displays true
The part where it gets complicated is that you must perform the test on an element that could conceivably be the owner of that event. For example, the onchange event is not produced by the document element, so testing against it will always return false.
alert('onchange' in document.documentElement); //displays false alert('onchange' in document.createElement('input')); //displays true
Coding with this in mind, we can associate certain events with their triggering elements, such as submit and reset with the form element. Other events would apply to numerous element types, including the DIV, so it is used as the default. The function below incorporates an event-to-element object to create the correct test element. It also accepts events with or without the “on” prefix, in the jQuery style:
function isEventSupported(eventName) { var TAGNAMES = { 'select': 'input', 'change': 'input', 'submit': 'form', 'reset' : 'form', 'error' : 'img', 'load' : 'img', 'abort' : 'img' } eventName = eventName.replace(/^on/, ''); var elt = document.createElement(TAGNAMES[eventName] || 'div'); var eventIsSupported = ('on'+eventName in elt); elt = null; return eventIsSupported; } alert(isEventSupported('click')); //true alert(isEventSupported('onchange')); //true alert(isEventSupported('splat')); //false
The “Firefox” Technique
Some browsers, such as Firefox, are known to create methods on an element when an attribute with the same name as a supported event is set on that element:
var elt = document.createElement('div'); alert('before: ' + (typeof elt.onclick)); //object el.setAttribute('onclick', 'return;'); alert('after: ' + (typeof elt.onclick)); //function in Firefox, string in IE elt.setAttribute('onclick2', 'return;'); alert('after: ' + (typeof elt.onclick2)); //"undefined" in Firefox, string in IE
We can incorporate that feature into our function for even more robust operation:
//... var elt = document.createElement(TAGNAMES[eventName] || 'div'); eventName = 'on'+eventName; var eventIsSupported = (eventName in elt); if (!eventIsSupported) { elt.setAttribute(eventName, 'return;'); eventIsSupported = typeof elt[eventName] == 'function'; } elt = null; return eventIsSupported; //...
Testing For Events on the Global Window Object
Certain events like onunload and onresize pertain exclusively to the global window object. We can add it to the element array, but we have to be careful in our handling of the window, since one cannot be created using createElement(). A ternary operator tests for out special ‘win’ element type so that elt points to the window object without the use of createElement():
var TAGNAMES = { //... 'unload': 'win', 'resize': 'win' }, eventIsSupported; eventName = eventName.replace(/^on/, ''); var elt = TAGNAMES[eventName] == 'win' ? window : document.createElement(TAGNAMES[eventName] || 'div'); eventName = 'on'+eventName; eventIsSupported = (eventName in elt);
Caching Results
Ideally you would want to save results to a variable for later reference, but, if you were to perform a check every time that an event fires, it might be useful to have caching going on within the method itself. That’s easy enough to do using a closure, so that the cache resides in memory between function calls. Here then is the entire function showing how the closure is created using an outer inline method call:
var isEventSupported = function() { var cache = {}; return function(eventName) { var TAGNAMES = { 'select': 'input', 'change': 'input', 'submit': 'form', 'reset' : 'form', 'error' : 'img', 'load' : 'img', 'abort' : 'img', 'unload': 'win', 'resize': 'win' }, shortEventName = eventName.replace(/^on/, ''); if(cache[shortEventName]) { return cache[shortEventName]; } var elt = TAGNAMES[shortEventName] == 'win' ? window : document.createElement(TAGNAMES[shortEventName] || 'div'); eventName = 'on'+shortEventName; var eventIsSupported = (eventName in elt); if (!eventIsSupported) { elt.setAttribute(eventName, 'return;'); eventIsSupported = typeof elt[eventName] == 'function'; } elt = null; cache[shortEventName] = eventIsSupported; return eventIsSupported; }; }(); alert(isEventSupported('click')); //true alert(isEventSupported('onchange')); //true alert(isEventSupported('splat')); //false alert(isEventSupported('unload')); //true alert(isEventSupported('unload')); //true (from cache)
Conclusion
I make no claims that this is the greatest event testing code ever written, but it does give you some idea as to what can be done to ascertain whether or not a given event is supported by the browser. Feel free to take what I’ve done here and use it as a starting point for your own development.