DOM Event inconsistencies between browsers

I've been working on some bugs in the Calendar extender from the ASP.NET AJAX Control Toolkit and have been resolving some of the focus/blur events.  I've been trying to make the calendar work consistently across all of the browsers and found some frustrating differences in event firing order between the browsers.

Here are a list of some of the DOM events supported by the various browsers I tested:

 

Table 1 - DOM Events by Browser

Event DOM Level IE FF OP SF
DOMActivate Level 2       *
activate 2 *      
DOMFocusIn Level 2     1 1
focusin 2 *      
focus Level 0 * 1 1 1
DOMDeactivate Level 2        
deactivate 2 *      
DOMFocusOut Level 2     1 1
focusout 2 *      
blur Level 0 * 1 1 1
mousedown Level 0 * * * *
mouseup Level 0 * * * *
click Level 0 * * * *

IE: Internet Explorer 7.0
FF: Firefox 2.0.0.6
OP: Opera 9.23
SF: Safari 3.03 Beta (Windows)
*: Implemented on any element
1: Implemented only on specific elements
2: IE-only implementation

 

Internet Explorer uses activate/focusin/focusout instead of DOMActivate/DOMFocusIn/DOMFocusOut for events.  Interestingly only IE and Safari support DOMActivate/activate on any element.  Another thing I saw was that IE is the only browser which fires focus/blur events for any element.  Other browsers can support the focus/blur events on any element that has a tab index however.

The next version of Calendar needs to support the following activation scenarios depending on whether it is also associated with a button:

  • No Button
    • Show Popup when textbox receives focus
    • Show Popup when textbox is clicked
    • Hide Popup when textbox loses focus
    • Hide Popup when textbox receives ESC keypress
    • Hide Popup when a date is selected
  • Button
    • Show Popup when the button is clicked
    • Hide Popup when the button loses focus
    • Hide Popup when the button receives ESC keypress
    • Hide Popup when a date is selected

The issue is that when the textbox or button loses focus the popup should hide unless the focus is being directed at the popup itself.  The problem is that only IE recognizes a focus event on the popup's DIV tag when the popup is not part of the tab order (and we don't want the popup in the tab order). I was curious the order of DOM events fired in each browser so I wrote a small test page and stepped through for each.  The results are as follows:

 

Table 2 - DOM Event Order by browser

IE FF OP SF
  1. // click on INPUT
  2. INPUT: mousedown
  3. INPUT: activate
  4. INPUT: focusin
  5. INPUT: focus
  6. INPUT: mouseup
  7. INPUT: click
  8. // click on DIV
  9. DIV: mousedown
  10. INPUT: deactivate
  11. INPUT: focusout
  12. DIV: activate
  13. DIV: focusin
  14. INPUT: blur
  15. DIV: focus
  16. DIV: mouseup
  17. DIV: click
  18. // click away
  19. DIV: deactivate
  20. DIV: focusout
  21. DIV: blur
  1. // click on INPUT
  2. INPUT: mousedown
  3. INPUT: focus
  4. INPUT: mouseup
  5. INPUT: click
  6. // click on DIV
  7. DIV: mousedown
  8. INPUT: blur
  9. DIV: mouseup
  10. DIV: click
  11. // click away
  1. // click on INPUT
  2. INPUT: focus
  3. INPUT: DOMFocusIn
  4. INPUT: mousedown
  5. INPUT: mouseup
  6. INPUT: click
  7. // click on DIV
  8. INPUT: blur
  9. INPUT: DOMFocusOut
  10. DIV: mousedown
  11. DIV: mouseup
  12. DIV: click
  13. // click away
  1. // click on INPUT
  2. INPUT: mousedown
  3. INPUT: focus
  4. INPUT: DOMFocusIn
  5. INPUT: mouseup
  6. INPUT: click
  7. INPUT: DOMActivate
  8. // click on DIV
  9. DIV: mousedown
  10. INPUT: blur
  11. INPUT: DOMFocusOut
  12. DIV: mouseup
  13. DIV: click
  14. DIV: DOMActivate
  15. // click away

 

Immediately a few things become obvious. First, I obviously cannot rely on focus/blur on the other browsers. IE would allow me to capture focusin on the DIV before the blur fires on the INPUT but that's just IE. Second, on all browsers except Opera the mousedown event of the DIV fires before the blur event of the INPUT. There's no clean sequential way to handle the focus transition in all browsers.

In the current version of Calendar we're making use of a "threading" class I created for the toolkit called DeferredOperation.  DeferredOperation wraps a delegate and executes it asynchronously using setTimout.  It has some built-in synchronization semantics and was used to handle the focus changes while the popup DIV was part of the tab order (which again, we don't really want). This introduces a subtle delay between activation requests and is problematic on Safari (we had to wait a full second before processing the deferred operation to let safari to catch up).

What I've opted for now is to do the best I can in three out of four of the browsers.  I track mousedown/mouseup using a flag and in Internet Explorer, Firefox, and Safari and monitor the flag in the blur event of the textbox. To handle the Opera inconsistency I again use a DeferredOperation to asynchronously handle the blur event which fixes the issue in that browser.

The final issue I discovered was that in all browsers, clicking on the popup causes the textbox (or button) to lose focus which again causes problems with the tab order.  On the new Calendar I reset the focus to the textbox (or button) on mouseup on the popup.

Hopefully these changes should make the next toolkit release so keep an eye out for the next update.