75
votes

It appears that the Blur event stops the click event handler from working? I have a combo box where the options only appear when the text field has focus. Choosing an option link should cause an event to occur.

I have a fiddle example here: http://jsfiddle.net/uXq5p/6/

To reproduce:

  1. Select the text box
  2. Links appear
  3. Click a link
  4. The blur even occurs and the links disappear
  5. Nothing else happens.

Expected behavior:

On step 5, after blur occurs, the click even should also then fire. How do I make that happen?

UPDATE:

After playing with this for a while, it seems that someone has gone to great lengths to prevent an already-occurred click event from being handled if a blur event makes the clicked element Un-clickable.

For example:

$('#ShippingGroupListWrapper').css('left','-20px');

works just fine, but

$('#ShippingGroupListWrapper').css('left','-2000px');

prevents the click event.

This appears to be a bug in Firefox, since making an element un-clickable should prevent future clicks, but not cancel ones that have already occurred when it could be clicked.

Other things that prevent the click event from processing:

$('#ShippingGroupListWrapper').css('z-index','-20');
$('#ShippingGroupListWrapper').css('display','none');
$('#ShippingGroupListWrapper').css('visibility','hidden');
$('#ShippingGroupListWrapper').css('opacity','.5');

I've found a few other questions on this site that are having similar problems. There seem to be two solutions floating around:

  1. Use a delay. This is bad because it creates a race condition between the hiding and the click event handler. Its also sloppy.

  2. Use the mousedown event. But this isn't a great solution either since click is the correct event for a link. The behavior of mousedown is counter-intuitive from a UX perspective, particularly since you can't cancel the click by moving the mouse off the element before releasing the button.

I can think of a few more.

3.Use mouseover and mouseout on the link to enable/disable the blur event for the field. This doesn't work with keyboard tabing since the mouse is not involved.

4.The best solution would be something like:

$('#ShippingGroup').blur(function()
{
   if($(document.activeElement) == $('.ShippingGroupLinkList'))
      return; // The element that now has focus is a link, do nothing
   $('#ShippingGroupListWrapper').css('display','none'); // hide it.
}

Unfortunately, $(document.activeElement) seems to always return the body element, not the one that was clicked. But maybe if there was a reliable way to know either 1. which element now has focus or two, which element caused the blur (not which element is blurring) from within the blur handler. Also, is there any other event (besides mousedown) that fires before blur?

7
The problem is the blur event is firing before the click. Since the blur hides those elements, you end up clicking nothing.vcsjones
I'm not sure the hiding is the problem. I tried setting the opacity to 50% instead of hiding them and the click event still does not occur. Please see jsfiddle.net/75sgt/1Nick
After further experimentation, it's the new un-clicability thats retroactively "un-clicking" an already clicked element. See my update above.Nick

7 Answers

90
votes

click event triggers after the blur so the link gets hidden. Instead of click use mousedown it will work.

$('.ShippingGroupLinkList').live("mousedown", function(e) {
    alert('You wont see me if your cursor was in the text box');
});

Other alternative is to have some delay before you hide the links on blur event. Its upto you which approach to go for.

Demo

4
votes

You could try the mousedown event instead of click.

$('.ShippingGroupLinkList').live("mousedown", function(e) {
    alert('You wont see me if your cursor was in the text box');
});

This is clearly not the best solution as a mousedown event is not achieved the same way for the user than a click event. Unfortunately, the blur event will cancel out mouseup events as well.

3
votes

Performing an action that should happen on a click on a mousedown is bad UX. Instead, what's a click effectively made up of? A mousedown and a mouseup.

Therefore, stop the propagation of the mousedown event in the mousedown handler, and perform the action in the mouseup handler.

An example in ReactJS:

<a onMouseDown={e => e.preventDefault()}
   onMouseUp={() => alert("CLICK")}>
   Click me!
</a>
1
votes

4.The best solution would be something like:

$('#ShippingGroup').blur(function()
{
   if($(document.activeElement) == $('.ShippingGroupLinkList'))
      return; // The element that now has focus is a link, do nothing
   $('#ShippingGroupListWrapper').css('display','none'); // hide it.
}

Unfortunately, $(document.activeElement) seems to always return the body element, not the one that was clicked. But maybe if there was a reliable way to know either 1. which element now has focus or two, which element caused the blur (not which element is blurring) from within the blur handler.

What you may be looking for is e.relatedTarget. So when clicking the link, e.relatedTarget should get populated with the link element, so in your blur handler, you can choose not to hide the container if the element clicked is within the container (or compare it directly with the link):

$('#ShippingGroup').blur(function(e)
{
   if(!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget)) {
   // Alt: (!e.relatedTarget || $(e.relatedTarget) == $('.ShippingGroupLinkList'))

       $('#ShippingGroupListWrapper').css('display','none'); // hide it.
   }
}

(relatedTarget may not be supported in older browsers for blur events, but it appears to work in latest Chrome, Firefox, and Safari)

1
votes

If this.menuTarget.classList.add("hidden") is the blur behavior that hides the clickable menu, then I succeeded by waiting 100ms before invoking it.

setTimeout(() => {
  this.menuTarget.classList.add()
}, 100)

This allowed the click event to be processed upon the menuTarget DOM before it was hidden.

0
votes

I know this is a later reply, but I had this same issue, and a lot of these solutions didn't really work in my scenario. mousedown is not functional with forms, it can cause the enter key functionality to change on the submit button. Instead, you can set a variable _mouseclick true in the mousedown, check it in the blur, and preventDefault() if it's true. Then, in the mouseup set the variable false. I did not see issues with this, unless someone can think of any.

0
votes

I have faced a similar issue while using jQuery blur, click handlers where I had an input name field and a Save button. Used blur event to populate name into a title placeholder. But when we click save immediately after typing the name, only the blur event gets fired and the save btn click event is disregarded.

The hack I used was to tap into the event object we get from blur event and check for event.relatedTarget.

PFB the code that worked for me:

$("#inputName").blur(function (event) {
        title = event.target.value;
        //since blur stops an immediate click event from firing - Firing click event here
        if (event.relatedTarget ? event.relatedTarget.id == "btnSave" : false) {
            saveBtn();
        }
    });

$("#btnSave").click(SaveBtn)

As already discussed in this thread - this is due to blur event blocking click event when fired simultaneously. So I have a click event registered for Save Btn calling a function which is also called when blur event's related Target is the Save button to compensate for the click event not firing. Note: Didnt notice this issue while using native onclick and onblur handlers - tested in html.