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 |
|
|
|
|
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.
Comments
Anonymous
September 11, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/09/11/dom-event-inconsistencies-between-browsers/Anonymous
February 08, 2012
Though this article is somewhat dated, it proved valuable to me in solving a similar problem. Therefore I decided to share my solution. My problem was with a scrollbar in an autocomplete dropdown. Since the dropdown should be hidden when the form element it is attached to loses focus, maintaining focus on the correct element became an issue. When the scrollbar was clicked, only Firefox (10.0) kept focus on the input element. IE (8.0), Opera (11.61), Chrome (17.0) and Safari (5.1) all removed focus from the input, resulting in the dropdown being hidden, and since it was hidden, click events would not fire on the dropdown. Fortunately, the shift of focus can be easily prevented in most of the problem browsers. This is done by canceling the default browser action: dropdown.onmousedown = function(event) { // Do stuff return false; } Adding a return value to the event handler sorted out the problem on all browsers except IE. Doing this cancels the default browser action, in this case the focus shift. Also, using mousedown instead of click meant that the event handler would be executed before the blur event fired on the input element. This includes Opera, contrary to the information in the above article. This left IE as the only remaining problem (no surprise there). It turns out that there is no way to cancel the focus shift on IE. Fortunately, IE is the only browser that fires a focus event on the dropdown, meaning focus on the input element can be restored with an IE-exclusive event handler: dropdown.onfocus = function() { input.focus(); } This solution for IE is not perfect, but while the focus shift is not cancelable, this is the best you can do. What happens is that the blur event fires on the input, hiding the dropdown, after which focus fires on the now hidden dropdown, which restores focus on the input and triggers showing the dropdown. In my code it also triggers repopulating the dropdown, resulting in a short delay and loss of the selection, but if the user wants to scroll the selection is probably useless anyway, so I deemed this acceptable.