Detecting event support without browser sniffing

Detecting event support without browser sniffing


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

one of the pain points of feature testing in client-side scripting is that for  event support . DOM doesn’t really specify any means to detect exactly which events browser understands and can work with. If you’d like to know if a browser supports, say, “dblclick” event, you’re pretty much out of luck. This is probably the reason why so many scripts on the web employ  unreliable browser sniffing   in such cases. One of the most common events that people sniff for are IE’s proprietary mouseenter/mouseleave, Opera’s impotent contextmenu, and input-related onbeforepaste, onbeforecut, etc. which are present in IE and WebKit, but not in Mozilla-based browsers.

Since browser sniffing is completely unreliable (as well as unmaintainable and fragile), we need a better way to detect events.

An obvious solution might be to employ an actual testing – create an element, attach an event listener, fire an event from that element and check if event listener gets executed. This solution is unfortunately quite brittle and is often too cumbersome. Simulating key events, for example, is currently hardly supported across browsers. Moreover, many events can be considered too obtrusive and interfere with user experience. It is also possible that events such as “scroll” and “resize” are prevented by popup-blockers and so can not be reliably tested.

It’s not widely known, but there actually is a quite robust way to detect most of the DOM L2 events. The trick is that many modern browsersreport property corresponding to an event name as being existent in an element :

[javascript] view plain copy
  1. 'onclick' in document.documentElement; // true  
  2. 'onclick2' in document.documentElement; // false  

Unfortunately, this is not the case with Firefox. Besides, browsers that do support this, sometimes don’t allow to test an arbitrary event on an arbitrary element.  An event must be checked on an element that could actually originate that event :

[javascript] view plain copy
  1. 'onreset' in document.documentElement; // false  
  2.  'onreset' in document.createElement('input'); // true  

To work around Firefox, we can employ a slightly different strategy (recommended by  David Mark ). The workaround is based on the fact that some of the browsers, including Firefox, actually create methods on an element  when an attribute with the name corresponding to a “known” event is set on that element :

[javascript] view plain copy
  1. var el = document.createElement('div');  
  2.   
  3. el.setAttribute('onclick''return;');  
  4. typeof el.onclick; // "function"  
  5.   
  6. el.setAttribute('onclick2''return;');  
  7. typeof el.onclick2; // "undefined"  

Combining these two approaches, we can create a somewhat robust way to detect an event support. A generic  isEventSupported  function would look like:

[javascript] view plain copy
  1. var isEventSupported = (function(){  
  2.   var TAGNAMES = {  
  3.     'select':'input','change':'input',  
  4.     'submit':'form','reset':'form',  
  5.     'error':'img','load':'img','abort':'img'  
  6.   }  
  7.   function isEventSupported(eventName) {  
  8.     var el = document.createElement(TAGNAMES[eventName] || 'div');  
  9.     eventName = 'on' + eventName;  
  10.     var isSupported = (eventName in el);  
  11.     if (!isSupported) {  
  12.       el.setAttribute(eventName, 'return;');  
  13.       isSupported = typeof el[eventName] == 'function';  
  14.     }  
  15.     el = null;  
  16.     return isSupported;  
  17.   }  
  18.   return isEventSupported;  
  19. })();  

You can now check for “contextmenu” support with –  isEventSupported("contextmenu")  – instead of an inferior –navigator.userAgent.indexOf('Opera') > -1. If Opera “fixes” “contextmenu” event in its future versions, there’s a big chance that  isEventSupportedwill just evaluate to  true  and you won’t need to change a single line of code (to make whatever relies on “contextmenu” event – work)

You can also use a stripped down version of this function, for detecting, say, only Mouse events:

[javascript] view plain copy
  1. function isMouseEventSupported(eventName) {  
  2.     var el = document.createElement('div');  
  3.     eventName = 'on' + eventName;  
  4.     var isSupported = (eventName in el);  
  5.     if (!isSupported) {  
  6.       el.setAttribute(eventName, 'return;');  
  7.       isSupported = typeof el[eventName] == 'function';  
  8.     }  
  9.     el = null;  
  10.     return isSupported;  
  11.   }  

And then use –  isMouseEventSupported("mouseenter")  instead of a horrendous –  (!!window.attachEvent && !window.opera)  :)

The only oddity I noticed with this method was IE reporting  false  for “unload” event. “unload” can still be easily checked in a global  windowobject –  "unload" in window  – returns  true  in all versions of IE that I tested (6-8). That expression can, of course, produce false positives if there’s a global “unload” variable, but, as a workaround, you can always try deleting the variable and see if  in  still returns  true.

A minor downside to this test is that it doesn’t allow to detect  Mutation Events . Fortunately, detecting those is not very complex. You can find an example of  a perfect feature test for DOMAttrModified   in Diego Perini’s NWMatcher.

I made  a simple test case , listing all of the events specified in DOM L2 and corresponding results of running  isEventSupported  on them.

Give it a try, and enjoy a feature testing!

你可能感兴趣的:(JavaScript,Web)