Friday, 6 November 2009

Testing for browser event support without sniffing

Browser Event Support by object detection

One 0f the things I have often wondered since I really got into Javascript a few years back was whether there was a way to check for event support without resorting to browser sniffing.

I had a task the other day which meant that I had to add some code to prevent a user from pasting in content into an email confirmation box. I used the onpaste event for most browsers but Opera doesn't support this so I had to use a keypress event to look for the CTRL+V key combo and then block it. This got me looking at ways of checking for browser event support without resorting to a sniff.

Then I came across this article by Ryan Morr: http://ryanmorr.com/archives/ondomready-no-browser-sniffing

A lot of the DOMReady functions I have seen used including my own use a browser sniff to check for old Opera, WebKit and KHTML and use a timer to check for loaded state, a call to DOMContentLoaded for DOM2 supported browsers, a cludge for IE using defer or a doScroll and then a fallback to window.onload to handle anything that doesn't fire by the time the window loads.

Ryan's solution is to do all of them without any browser checks. He adds a DOMContentLoaded listener for DOM2 browsers as well as a window.onload and then he sets a timer up for all browsers. All of these call a function which checks the appropriate event type and for IE does the doScroll check. Once a load has been confirmed the desired function is called and the timer is killed.

Its a shame that a timer has to be used for all browsers when in reality only a very small percentage of browsers will fall into the class that require it however its an example of thinking outside the box.

This somehow got me to another article by Kangax where he had a brilliant function for checking for event support in any browser using a combination of two methods:
var isSupported = ('onpaste' in element)
and for those that fail a creation of the event with a simple return as the function and then a check to make sure that the event is a typeof function e.g
el.setAttribute('onpaste', 'return;');
isSupported = typeof el['onpaste'] == 'function';

So I read some more and then checked out some similar articles and his test page which had a number of event tests and saw that in IE and Chrome/Safari that the unload and resize tests failed using the existing checks. These events are definitely supported so should result in a positive when tested for. Therefore I have amended the original function to use the window object for these checks if the first check fails. I have also added a little cache in to prevent the same event type being checked multiple times as well as combining another check by Diego Perini which checks the global Event object. I don't actually know if this last check is required as I haven't seen a browser where the first tests fail but its there anyway.

var isEventSupported = (function(){
var win=this,
cache={},
TAGNAMES = {
'select':'input','change':'input',
'submit':'form','reset':'form',
'error':'img','load':'img','abort':'img'
};
function isEventSupported(eventName) {
var key = (TAGNAMES[eventName] || (eventName=="unload"||eventName=="resize")?"window":'div') + "_" + eventName;
if(cache[key])return cache[key];
var el = document.createElement(TAGNAMES[eventName] || 'div');
var oneventName = 'on' + eventName.toLowerCase();
var isSupported = (oneventName in el);
// cannot create a window object so to get a correct test for IE/Webkit on resize/unload check the window
if(!isSupported && (eventName=="unload" || eventName=="resize")){
isSupported = (oneventName in win);
}
if (!isSupported && el.setAttribute) {
el.setAttribute(oneventName, 'return;');
isSupported = typeof el[oneventName] == 'function';
}
// the above tests should work in majority of cases but this test checks the EVENT object
if(!isSupported && win.Event && typeof(win.Event)=="object"){
isSupported = (eventName.toUpperCase() in win.Event);
}
el = null;
cache[key]=isSupported;
return isSupported;
}
return isEventSupported;
})();


You can check out the test page here which compares a number of events against this function as well as Kangax's original and also a version by Diego Perini who I believe was the person who came up with the doScroll method for IE used in many a DOMReady function.

http://www.strictly-software.com/eventsupport.htm

Unfortunately this method of event testing doesn't work with the mutation events such as DOMContentLoaded but then the only way you can really test for these is by running them anyway. Therefore although this function was perfect for my onpaste check it wouldn't work in a DOMReady function to test whether DOMContentLoaded was supported or not.

Articles Mentioned:

http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing

http://ryanmorr.com/archives/ondomready-no-browser-sniffing

1 comment:

Ittay Dror said...

This doesn't work to check <link/> 'load' event in FF4. Once the 'onload' property is set on the link element, the typeof check returns true.