allenc allencheung

Javascript DOM blocking

Recently I’ve found myself repeating a pattern in my Javascripts that works, but I’m not exactly sure why. I’ve tried to rationalize how the underlying system might work, but it’s kind of half-assed and I’m not convinced that it’s accurate in the slightest.

The code looks like this:

<style>
.hidden { display: none; }
.show { display: block !important; }
</style>

<script>
  var $input = $('input[type=text].hidden');
  $input.addClass('show');
  $input.focus();
</script>

That is, all I want to do is display a previously hidden input element and then set focus on it. Seem straightforward, except that this code as its written does not set the focus properly, at least in webkit browsers; focus() has no effect on an input that is not visible on the screen, and I’m guessing that after the addClass() the browser is still in the midst of actually adding the class and/or reflowing the layout. It’s Javascript’s asynchronicity run amuck.

My hacky workaround is to wrap the focus call around a setTimeout():

$input.addClass('show');
window.setTimeout(function() { $input.focus(); }, 0);

Like most people, I started by setting a “fudge factor” of 100ms or 200ms, in hopes that the browser would be done with whatever it has to do. Of course, it’s wrong to have arbitrary delays in your UI (which wouldn’t even guarantee correctness on slow browsers), so I started shaving ms’s off, until to my surprise 0ms works just as well.

My theory is that setTimeout() here acts as a blocking function, effectively waiting until the addClass() is completely done before executing its callback. This kind of makes sense if you remember that plain ol’ Javascript is single-threaded, which means that timing functions like setTimeout() and setInterval() don’t promise to run at the exact delayed time, only at first able opportunity after the delayed period.

It just seems wrong that manipulating the DOM is considered an asynchronous event, especially since the DOM API doesn’t have the same callback hooks that other, more obvious async resource APIs have. Even if this were the case, setTimeout() is totally the wrong function to use, but I don’t know of a better one.

Has anybody else come across this? What are some more elegant solutions smarter people have found?

By allen
allenc allencheung

Elsewhere