12

Consider this example where I have 2 input fields:

<input id="a" />
<input id="b" style="display: none" />

And consider the following JavaScript, which is an attempt to do this:

Show #b only when #a has focus and hide #b whenever #a loses focus, except when #a loses its focus to #b.

$("#a").focus(function() {
    $("#b").show();
});

$("#a, #b").blur(function() {
    $("#b").hide();
});

$("#b").focus(function(){
    $("#b").show();
});

$("#a").focus(function() {
  $("#b").show();
});

$("#a, #b").blur(function() {
  $("#b").hide();
});

$("#b").focus(function() {
  $("#b").show();
});
#b {
  display: none;
}
<input id="a" value=a>
<input id="b" value=b>
<br/>^ focus on the input

The above code is incorrect as $("#b").focus() would never be triggered because as soon as #a loses focus, #b is hidden. This expected behavior is observed in Firefox (Version 24.6.0).

But in Chrome (Version 35.0), the code seems to run incorrectly (or correctly!?).

Clearly, the b.focus event is still being registered in Chrome. Why does this event register in Chrome, but not in Firefox?


Update

As pointed out by raina77ow:

  • In Chrome, after we place the cursor on b, blur on a is fired first, then focus on b, and b stays visible.
  • In Firefox, focus on b is not fired, so b becomes invisible.
  • In IE10, however, somehow focus on b IS fired, but b becomes invisible immediately, as blur is fired on b right after.

Here's a fiddle without using jQuery, producing the same behavior.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
John Bupit
  • 10,406
  • 8
  • 39
  • 75
  • Hmm, interesting. In both cases, naturally, `blur` Event just has to be fired before the `focus` one. What's different is that in Chrome `focus` on `#b` is still registered - firing up `#b.show()`. – raina77ow Jul 08 '14 at 15:23
  • 2
    Well, there's actually three different behaviours there. In Chrome, after we place the cursor in `b`, `blur on a` is fired first, then `focus on b`; `b` stays visible. In Firefox, `focus on b` is not fired, `b` becomes invisible. In IE10, however, somehow `focus on b` IS fired, but `b` becomes invisible immediately, as `blur` is fired on `b` right after. Go figure. – raina77ow Jul 08 '14 at 15:38
  • 2
    Here's [fiddle](http://jsfiddle.net/4dApN/) with all the console outputs. – raina77ow Jul 08 '14 at 15:39
  • 2
    Here's [the discussion](http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-September/245075.html) that seems to be related. Note two (slightly) connected issues in there. In short, it seems to be yet another case of unclear spec, implemented differently in browsers. – raina77ow Jul 09 '14 at 11:00
  • From the line's there: 'In Gecko, `display:none` elements don't receive key events'. – raina77ow Jul 09 '14 at 11:01

3 Answers3

9

As you know, the issue is that different browsers choose to call event handlers in different orders. One solution is to give the other events a chance to fire by setting a timer for 0 milliseconds, and then checking the fields to see which (if any) is focused.

a.onfocus = function() {show(b);};

a.onblur = function() {
    setTimeout(function() {
        //if neither filed is focused
        if(document.activeElement !== b && document.activeElement !== a){
            hide(b);
        }
            }, 0);
};

//same action as for a
b.onblur = a.onblur;

Tested in Chrome, Firefox, Internet Explorer, and Safari. See full working example (edited version of your fiddle) at JSFiddle.net.

Nateowami
  • 1,005
  • 16
  • 23
  • 2
    setTimeout(function(){}) is something like next tick and is a very bad practice for controlling a program code (if not done by purpose, for example, to give a program a breath to perform next queued task). Just try to call blur/unblur several times (let's say 1000) in FOR loop and you will notice asynchronous behavior which can break your code flow – igorpavlov Jul 17 '14 at 17:09
  • I agree -the problem is the program _does_ need a breath to perform the next queued task, which is allowing the `b` element to focus. Is there a better equivalent of Java's `Thread.yield()`? – Nateowami Jul 18 '14 at 00:33
  • `setTimeout(function{...}, 0);` is okay if you only need to flush the event queue before running your code. However, JavaScript specs often leave things undefined and just because you can make some code work in *one specific implementation* (e.g. Blink) but fluishing the event queue it doesn't mean that the same logic does work in all browsers. – Mikko Rantalainen Aug 23 '21 at 09:46
4

You can use an extravarible to check whether b is focused before hiding b. It worked in IE, Chrome & Firefox. I don;t have any other browser. You can check it.

var focusedB = false;
$("#a").focus(function(){
     $("#b").show();   
 });
 //if b is focused by pressing tab bar.
 $("#a").keydown(function(e){
     if(e.which === 9){
          focusedB = true;  
      }
   });
   $("#b").blur(function(){
        $("#b").hide();
   });
   $("#a").blur(function(){
       if(focusedB){
            focusedB = false;
        }else{
            $("#b").hide();
        }
    });
    $( "#b" ).mousedown(function() {
       focusedB = true;
    });
Indranil Mondal
  • 2,799
  • 3
  • 25
  • 40
  • 1
    The real difference here is not so much the extra variable as that jQuery calls the `mousedown` event before the `blur` event, even in Firefox. Add `console.log()` and you'll see this is the case. – Nateowami Jul 18 '14 at 02:31
  • I think we can't change the sequence of events in the specific browser, but as John said $("#b").focus() would never be triggered because as soon as #a loses focus, #b is hidden. But here I've the mousedown event before the blur so I'm using the extra variable, to check whether to hide b or not. It worked fine in firefox. – Indranil Mondal Jul 18 '14 at 14:52
0

According to this archive of whatwg.org:

An element is focusable if the user agent's default behavior allows it to be focusable or if the element is specially focusable, but only if the element is either being rendered or is a descendant of a canvas element that represents embedded content.

It seems that all browsers also don't make visibility:hidden and display:none input elements focusable. The following JavaScript tests in which cases is an element focusable.

function isFocusable(type) {
    var element = document.getElementById(type);
    result += type + ' is';
    try {
        element.focus();
        if (element != document.activeElement)
            result += ' not';
    } catch (e) {
      result += ' not (error thrown)';
    }
    result += ' focusable<br>';
}

var result = '';

function isFocusable(type) {
  var element = document.getElementById(type);
  result += type + ' is';
  try {
    element.focus();
    if (element != document.activeElement)
      result += ' not';
  } catch (e) {
    result += ' not (error thrown)';
  }
  result += ' focusable<br>';
}

isFocusable('text');
isFocusable('hidden');
isFocusable('disabled');
isFocusable('readonly');
isFocusable('visiblity-hidden');
isFocusable('display-none');
isFocusable('div-hidden');

document.getElementById('browser-version').innerHTML = navigator.userAgent;
document.getElementById('logger').innerHTML += result;
<input id=text type=""></input>
<input id=hidden type="hidden"></input>
<input id=disabled disabled></input>
<input id=readonly readonly></input>
<input id=visiblity-hidden style="visibility:hidden"></input>
<input id=display-none style="display:none"></input>
<div id=div-hidden sytle="visibility:hidden" tabindex=1>
  </input>

  <div id=browser-version></div>

  <div id=logger></div>

Here's the output in Firefox 34.0.5 and Chrome 39.0.2

Gecko/20100101 Firefox/34.0
text is focusable
hidden is not focusable
disabled is not focusable
readonly is focusable
visiblity-hidden is not focusable
display-none is not focusable
div-hidden is focusable

AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
text is focusable
hidden is not focusable
disabled is not focusable
readonly is focusable
visiblity-hidden is not focusable
display-none is not focusable
div-hidden is focusable
John Bupit
  • 10,406
  • 8
  • 39
  • 75