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?

Share this article
Shareable URL
Prev Post

The Early Life of Steve Jobs

Next Post

Some Thoughts on Engineering Salaries

Comments 13
    1. I’m inclined to believe that the wait time you’re describing is the interpreter/JIT compiler running through the rest of the function before running the actual setTimeout() call, rather than it delaying the setTimeout() randomly.

      1. It’s not random: http://www.w3.org/TR/html5/timers.html#timers
        For setTimeout():
        “If the currently running task is a task that was created by the setTimeout() method, and timeout is less than 4, then increase timeout to 4.”
        and for setInterval():
        “If timeout is less than 10, then increase timeout to 10.”

        1. Great find! This explains why setTimeout(func, 0) seems to work. I guess the next experiment would be to see whether something that would take >4 ms (or even, say 100 ms) would still be blocked by the setTimeout(); I’d guess no.

    1. Well, this is more of a toy problem, and I’ve run across other times where there’s less of a workaround (e.g., adding a style directly, hooking up an event handler).

  1. Also, you might consider chaining the code:
    $input.addClass(‘show’).focus();
    Ideally, focus will only be called once it gets the object(s) returned by addClass — automatically removing any async behaviour.

    1. I don’t think chaining helps; like you say, the problem is that I have to make the focus call happen after addClass has fully executed, and in JS that’s communicated via a callback.

  2. I’m not certain about this.. Just taking a stab in the dark, but would jQuery’s (assuming that’s what your $ is an instance of) show() help here?
    Ie. $input.show().focus()?
    Maybe jQ does some reflow-waiting magic in that function..

Leave a Reply

Your email address will not be published. Required fields are marked *

Read next