codewarrior
codewarrior

Reputation: 61

Single user action firing two events - second event doesn't trigger

I’m running into this issue where a single action by the user is supposed to trigger two events but it only triggers the first one.

The scenario:

A user enters some text into a special field that modifies the layout on focusout , after entering the text, without leaving the field, they click a button.

What’s happening?

I have a focusout event on a text box and click event on a button. What I see is the focusout event gets fired but the click event never does.

I’ve encapsulated this in a jsfiddle:

http://jsfiddle.net/fCz6X/13/

$('#theText').focusout(function (){
    $("#focusevent").text("Focusevent");
    console.log("focus");
});

$('#theButton').click(function (){
    $("#clickevent").text("Clickevent");
    console.log("click");
});

So if you click in the text field then click the button I’d expect both events to fire, but we only see the focus out event.

I put in a temporary fix for this by having the mousedown event fire the button instead of a click event (this fires before the focusout event) but that is causing some other behaviors and issues that I don’t want to see. Due to those I think optimal solution is finding a way to get the focusout and click events to both fire. Does anyone have thoughts on how to fix this problem?

Edit: After seeing initial responses I dug a little deeper, the issue here is that the focusout event is changing the page layout which very slightly pushes the location of the button down. The click event triggers after the focusout is done but since the button is no longer in the exact same location, nothing happens.

Here is an updated fiddle that shows my problem http://jsfiddle.net/fCz6X/11/

Upvotes: 1

Views: 386

Answers (5)

Dale Thompson
Dale Thompson

Reputation: 173

I had a similar issue in which the focusout function seemed to be preventing the click event from firing.

A click event consists of both a mousedown and a mouseup event. If a click invokes a focusout for the previous element, the focusout event occurs after the mousedown event and before the mouseup event of the subsequent element.

In my case, my focusout function invokes an action using the fetch API, during which an animated image is displayed on an overlay. The overlay prevented the mouseup event from being registered for the original element so that only the mousedown event was registered; no click event was produced.

My solution was (1) to set a global variable preserving a reference to the original element clicked and (2) to add a mouseup listener to the image overlay (whose id is "wait") to invoke the original element's click method (in those instances in which the click was released while the overlay was still visible):

The function invoked by the focusout event, which calls the fetch API wrapper, must include its related element as a parameter:

// function called by a focusout event
function myResponse(e) {
  myRequest = 'whatever';
  return myFetch('save.php', myRequest, true, e.relatedTarget).then(response => {
...
}

The element clicked is set to a global variable:

let relatedTarget;
// simulate the original click
function finishClick(e) {
  if (relatedTarget) {
    relatedTarget.click();
    relatedTarget = null;
  }
}
// but not if both mousedown and mouseup registered on the overlay
function unsetRelatedTarget(e) {
  relatedTarget = null;
}

// custom wrapper for fetch API
async function myFetch(page, request, wait, relatedTargetParam) {
  if (wait) {
    wait = document.getElementById('wait');
    if (wait) {
      wait.style.display = 'block';
      if (relatedTargetParam) {
        relatedTarget = relatedTargetParam;
        wait.addEventListener('mousedown', unsetRelatedTarget);
        wait.addEventListener('mouseup', finishClick);
      }
    }
  }
  return await fetch(page,
    {
      method: 'POST',
      body: request
    }
  ).then(response => {
...

An alternate solution is to use merely a mousedown event instead of a click event when there is a possibility of a focusout event interrupting the click. A downside to this solution is that a user can no longer abort the click event by moving the mouse away from the element before releasing the clicker.

Upvotes: 0

Mike Edwards
Mike Edwards

Reputation: 3771

As Joe said, the blocking alert call is what is breaking the event. Using a non-blocking call you will see both events.

If you really need to perform an alert like this, though, you can defer calling 'alert' until later using setTimeout()

$('#theText').focusout(function (){
    setTimeout(function() { // alert after all events have resolved
        alert("focus left text box");
    }, 0);
});

Edit: In your updated fiddle the reason the click event never fires is because no click event occurs. If you move the button out from under the mouse on mousedown, there is no followup mouseup which is what initiates the 'click' event.

You may need to reconsider other aspects of your design. Your solution of using 'mousedown' is the best you can achieve because it's the only event that actually occurs.

Upvotes: 0

Tom Pietrosanti
Tom Pietrosanti

Reputation: 4294

I believe this is because the focusout event fires first, executing the handler, and the alert then prevents the browser from recognizing the click.

Try again with console.log instead of alert - it's less invasive.

Upvotes: 0

gasp
gasp

Reputation: 596

It is the Alert that is blocking. Some browser security prevents firing too many window.alert at the time.

When trying with other triggers, it looks. You may try console.log()

$('#theText').on("focusout",function (){
    $("#theText").val($("#theText").val()+"flb");   
});

$('#theButton').on("click",function (){
    $("#theText").val($("#theText").val()+"but");
});

Upvotes: 0

Joe Enos
Joe Enos

Reputation: 40403

It's because you're calling alert - the focusout event fires, but before the browser recognizes you've clicked the button, the alert box blocks it.

Change your event handler to console.log or something else that's non-obtrusive and you'll be ok.

Upvotes: 1

Related Questions