3
votes

So I have yet another little JavaScript issue with my current project: JS events aren't firing in the order that I expect them to.

Let's say I've got a text field with an onBlur event that fires an AJAX call that does some calculating on the server (I'm using .NET 3.5's WebServices with JSON). Its callback method populates a page full of text fields with results from the calculation.

I've also got a button with an onClick event that initiates a different calculation, but it relies on having finished the text field's onBlur event(s) before the results of its onClick event will be valid.

I have a simple mutex in place which works fairly well, until the following situation: The user changes a value in one of the text fields but has not yet left it, so focus is still on the text field. While the text field has focus, they click a button that has an onClick event. More often than not, the browser (IE7 in this case) fires the onClick event of the button BEFORE the onBlur event of the text field!

This causes some havoc, and I have no clue how to enforce the order of the events being fired. My little mutex works as follows: when any of the events are fired, I set a flag that means "wait for the callback." Any subsequent events that fire check the flag before doing anything. In the callback a reset the flag to false.

So, any ideas? I guess I almost want to make all of the web service calls synchronous, but I don't know if there's an easy way to do that.

Other things I've tried or things you should know: - Disabling the buttons in the onBlur of the text fields -- their onClicks still fire before they get disabled - Keep in mind, this only happens when editing a text field and immediately clicking a button. If the user clicks on empty space to fire the onBlur of the text field before clicking the button, it all works perfectly.

EDIT:

After some comments and more testing here's what I'd like to be able to do:

In the onBlur of the text field, I want to disable the button so that its onClick event doesn't fire. However, when I code this up, the onBlur event fires, I can see the button getting disabled, but its onClick event is still firing.

Here's some sample code. Sometimes the onClick event happens, and sometimes it doesn't. If you up the setTimeout to 500ms or so, it seems to always prevent it. Maybe this is an option...

<html>
<head>
<script type="text/javascript">

var onClickFlag = false;

function whatHappensOnFocus(el) {
    log('whatHappensOnFocus(): TextField focused.');
    onClickFlag = false;
    log('whatHappensOnFocus(): Set onClickFlag = false');
}

function whatHappensOnBlur(el) {
    log('whatHappensOnBlur(): TextField blurred.');
    document.getElementById('theButton').disabled = true;
    log('whatHappensOnBlur(): Disabled the Button');
    setTimeout(function() { someTestFieldFunction(); log('whatHappensOnBlur(): OnBlur callback called: someTestFieldFunction()'); }, 50);
    log ('whatHappensOnBlur(): setTimeout for someTestFieldFunction() for 50ms');
}

function log(msg) {
    document.getElementById('log').value += ('[' + (new Date().getTime()).toString() + '] ' + msg + '\n');
}

function someTestFieldFunction() {
    log('someTestFieldFunction(): Inside someTestFieldFunction()');
    log('someTestFieldFunction(): Test: onClickFlag = ' + onClickFlag);
    //alert('you blurred the text field.');
    //alert('onClickFlag = ' + onClickFlag);
    setTimeout(enableButton, 50);
    log('someTestFieldFunction(): setTimeout for enableButton()');
}

function enableButton() {
    document.getElementById('theButton').disabled = false;
    log('enableButton(): Button re-enabled');
    //onClickFlag = false;
}

function whatHappensOnClick(el) {
    log('whatHappensOnClick(): the Button onClick fired');
    someFunction();
    log('whatHappensOnClick(): someFunction() called');
}

function whatHappensOnMouseDown(el) {
    log('whatHappensOnMouseDown(): inside whatHappensOnMouseDown()');
    onClickFlag = true;
    log("whatHappensOnMouseDown(): set onClickFlag = true");
}

function someFunction() {
    log('someFunction(): inside someFunction()');
    log('someFunction(): You have successfully clicked the button');
    //alert("you clicked me! this shouldn't happen!")
}

</script>
</head>
<body>
<form id="testform">
<input type="text" size="10" onfocus="whatHappensOnFocus(this);" onblur="whatHappensOnBlur(this);" />
<br />
<br />
<input id="theButton" type="button" onmousedown="whatHappensOnMouseDown(this);" onmouseout="onClickFlag = false;" value="calculate" onclick="whatHappensOnClick(this);" />
<br />
<br />
<textarea rows="15" cols="100" id="log" style="overflow: scroll"></textarea>
<input type="reset" value="clear" />
</form>
</body>
</html>

UPDATE:

I updated the code I posted to reflect some of the issue, so you can see how I tested this. When you have focus on a text field and then click on a button, the onMouseDown event of the button fires BEFORE the onBlur event of the text field. Because of this, I can set a flag so that I know they tried to click the button. Then in the onSuccess callback event of the AJAX call fired by the onBlur event of the text field, I can see if the button flag is set (so I know they tried to click it) and fire that event (disabling everything else so it can complete). There are only a few buttons on the form that need this behavior, so I think this solution will be sufficient for now.

6
Just did a quick test, and the blur event always fires first for me in IE7.Josh Stodola
Really? I believe you... let me try a quick test as well...Cᴏʀʏ
Ok, you're right. I've edited the question because I still have whacky behavior.Cᴏʀʏ

6 Answers

4
votes

Are you using AddEventListener to set up the events? If so, what are you passing in for the third parameter?

This may be helpful:

http://www.quirksmode.org/js/events_order.html

1
votes

What about using js to cause the textbox to lose focus after it has finished doing what it needs to?

0
votes

I doubt the order of events are the source of your problem. I just tried the following code in IE7, IE8 and Chrome and the order of events is always blur then click.

<script>
    function dbg(t) {
        document.getElementById("dbg").value += t + "\n";
    }
</script>
<input type="text" onblur="dbg('text.blur')" />
<input type="button" value="PressMe" onclick="dbg('button.click')" />
<textarea id="dbg" cols="50" rows="10">
</textarea>

If I follow you correctly, you are doing some calls that require other calls completed before. This doesn't seems a very good approach, and you will either encounter bugs or high complexity of code with this approach.

Please try to explain what you do (from functional point of view) and try to find alternative ways of doing it, instead of trying to work out a complex technical approach.

0
votes

I assume your click event relies on the result of the blur event. In that case, I think the best thing to do is set the button to be disabled (or hide it), and then enable it when your onblur request is finished.

<input type="submit" disabled="disabled" />

And the pseudo-Javascript...

onblur = function() {
  //ajax complete
  mybutton.disabled = false;   
}
0
votes

The easiest way (and probably the WORST, and by FAR), would be to make the program wait a certain time before proceeding to what you wanted it to do in the first place, this way, you could control the order of the callbacks.

As I said, this is probably the worst way to do it, but it's the only one I can think of. Here what it should look like :

function onClickHandler(callback)
{
    setTimeout(callback, 50);
}

function onBlurHandler(callback)
{
    //...
}

This way, your callback function on onBlur would always happen first.

0
votes

The problem with a fixed timeout of 50ms is that you're relying on time to solve the problem. What if the computer is slow for some reason, or you're running on a slow Netbook or an old computer or an old browser or mobile? Maybe you should use setInterval instead of setTimer and keep waiting until you verify that the disabling actually happened.