Blog

Wrestling with the Browser Event Queue

17 May, 2014
Xebia Background Header Wave

In this post I want to show some things I’ve learned recently about the browser event queue.
It all started with the following innocent looking problem:

I was building a single page web application, and on one form the user should have two options to enter a stock order.
The options were represented as radio buttons, each of them having its own input field. The radio buttons gave the user the choice: he could either enter a order amount, or a order quantity.
Both fields should have their own specific input validations which should be triggered when the user left the input field.

Here’s the Html snippet:

[html]
<form>
<input type=”radio” name=”options” id=”option_amount”>Amount</input>
<input id=”option_mount_input”/>
<input type=”radio” name=”options” id=”option_quantity”>Quantity</input>
<input id=”option_quantity_input”/>
</form>
[/html]

And the Javascript:

[javascript]
function validate_amount() {
if(!$.isNumeric($(“#option_amount_input”).text())) {
alert(“Please enter a valid amount”);
}
}

$(“#option_amount_input”).on(“blur”, validate_amount);
[/javascript]

All was working fine, except for one scenario: what if the user entered an amount in a wrong way, but then decided he wanted to enter a quantity so he clicked the quantity option instead.
In that case I wouldn’t want the validation for the amount to go off.

Here is my first attempt to fix this scenario:

[javascript]
var shouldValidateAmount = true;

function validate_amount() {
setTimeout(delayedAmountValidation, 0);
}

function click_quantity() {
shouldValidateAmount = false;
}

function click_amount() {
shouldValidateAmount = true;
}

function delayedAmountValidation() {
if(shouldValidateAmount) {
if(!$.isNumeric($(“#option_amount_input”).text())) {
alert(“Please enter a valid amount”);
}
}
}

$(“#option_amount_input”).on(“blur”, validate_amount);
$(“#option_quantity”).on(“click”, click_quantity);
$(“#option_amount”).on(“click”, click_amount);
[/javascript]

The idea is that when the user leaves the amount field, the amount validation will be triggered, but after a timeout. The timeout is zero so the validation event is just put at the end of the event queue.
So before the validation will be executed, the click handler for the quantity radio option will be handled first which will then cancel any postponed validations. (or so I thought)

But this didn’t work. For some reason the postponed validation was still being executed before the click handler.
Only if I adjusted the timout delay until it was high enough, in my case that was 70ms, it would be executed after the click handler. But that would be a very brittle solution of course.

Here’s a diagram showing how the event queue handled my solution:

event queue
So what to do about this? Time to look at the browser event queue in some more depth.

It turns out that browser considers certain groups of event as batches. Those are groups which the browser considers to be logically belonging together.
An example is a blur-event for one input combined with a focus-gained event for another input.
Timeouts generated from within a batch have to wait until all the events from their batch are handled, and only then get a chance to execute.

A blur-event and a click event on another element do apparently not belong to the same batch. Therefore my timeout was executed immediately after the focus-lost event was handled, and before the click event handler.

To listen to the focus event on the radio option was no solution of course, because just because it got focus didn’t necessarily mean it was seleced; the user could have tabbed there as well.
But it turns out that there are more events than just onFocus that belong to the same batch as blur; mouseDown and mouseEnter events!

Here is the final solution, which listens for a mouseDown event on the radio button:

event queue batch
And the code:

[javascript]
var shouldValidateAmount = true;

function validate_amount() {
setTimeout(delayedAmountValidation, 0);
}

function click_quantity() {
shouldValidateAmount = false;
}

function click_amount() {
shouldValidateAmount = true;
}

function delayedAmountValidation() {
if(shouldValidateAmount) {
if(!$.isNumeric($(“#option_amount_input”).text())) {
alert(“Please enter a valid amount”);
}
}
}

$(“#option_amount_input”).on(“blur”, validate_amount);
$(“#option_quantity”).on(“mousedown”, click_quantity);
$(“#option_amount”).on(“mousedown”, click_amount);
[/javascript]

References:
Link to Mozilla doc on events and timing

Qxperts. We empower companies to deliver reliable & high-quality software. Any questions? We are here to help! www.qxperts.io

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts