34

i have:

<input type="text" />

and

$('input').blur(function(){
    alert('stay focused!');
});

I want to prevent the blur function running when I'm "blurring" by clicking on an anchor element.

I.E. if i tab to another input, click somewhere on the page etc i want the blur to fire, but if i click a link, I don't want it to fire.

Is this easily achievable, or do i need to hack about with delegates and semaphores?

Thanks

Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231

5 Answers5

60

I had to solve this problem myself today, too. I found that the mousedown event fires before the blur event, so all you need to do is set a variable that indicates that a mousedown event occurred first, and then manage your blur event appropriately if so.

var mousedownHappened = false;

$('input').blur(function() {
    if(mousedownHappened) // cancel the blur event
    {
        alert('stay focused!');
        $('input').focus();
        mousedownHappened = false;
    }
    else   // blur event is okay
    {
        // Do stuff...
    }
});

$('a').mousedown(function() {
    mousedownHappened = true;
});

Hope this helps you!!

Alex B
  • 1,438
  • 13
  • 17
  • 1
    is this "mousedown before blur" behaviour reliable cross browser? if so, cool! – Andrew Bullock Feb 27 '12 at 09:26
  • 1
    Amazing solution! Thanks for posting... :D – Leniel Maccaferri May 29 '12 at 21:52
  • 1
    I ended up using your solution as well. It isn't the prettiest solution, but it does work. Plus you don't have to muck about with setTimeout with this solution. – Matt Jensen Nov 08 '12 at 23:56
  • 1
    Good solution, also working on iphone mobile safari to prevent hiding of the keyboard. – adriendenat May 15 '13 at 12:37
  • good solution, although it didn't work for me out of the box (tested in Chrome): namely, $('input').focus() wouldn't set focus on the input. My solution was to wrap it in setTimeout (without time value) like that: setTimeout(function(){ $('input').focus(); }); – Konstantin K May 21 '13 at 13:52
  • @AlirezaFallah No, sadly that person shamelessly copied and pasted my answer. If you notice his answer was posted on Dec 5 2012, and my answer was posted on Feb 5 2012. – Alex B Mar 04 '14 at 04:58
  • 8
    You may be better off canceling the mousedown event rather than setting a boolean that is used during the propagation of the event to the blur method. Cancel it with `e.preventDefault()` where `e` is the first parameter to the event handler function. – Preston S Aug 29 '14 at 21:39
  • 1
    @PrestonS: **That's exactly how it should be done.** Suppress *mousedown* event on external element to prevent *blur* event on input. And the best thing is that **suppressing *mousedown* event doesn't prevent *click* event from firing.** Perfect! – Robert Koritnik Oct 07 '15 at 10:27
  • Brilliant solution. Thank god for the underrated 'mousedown' event! 'click' always grabs the limelight, normally. – Charles Robertson Mar 04 '18 at 14:55
  • I am using it with 'e.preventDefault()'. Slightly cleaner solution, but the 'mousedown' find is the real show stopper... – Charles Robertson Mar 04 '18 at 14:56
23

If you want to keep the cursor at its position in a contenteditable element, simply:

$('button').mousedown(function(){return false;});
Jens Jensen
  • 1,038
  • 1
  • 10
  • 20
  • 7
    If you'd write proper code you'd likely outvote accepted answer. The proper way to do this is `$("button").mousedown(function(evt) { evt.preventDefault(); });` which will prevent *blur* event from triggering. – Robert Koritnik Oct 07 '15 at 10:31
7

Delay the blur a bit. If the viewer clicks a link to another page, the page should change before this code gets a chance to run:

$('input').blur(function(){
    setTimeout(function() {alert('stay focused!');}, 1000);
});

You can experiment with what delay value for the timeout seems appropriate.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    i dont want to have to wait for the blur to run when its a "legitimate" blur situation. Yes this works, but its not very elegant :( – Andrew Bullock Oct 01 '11 at 18:24
  • Blur gets called before anything else and there doesn't appear to be any way to know what is coming next after the blur event (e.g. whether it's a click on a link or not). So, the ONLY way to defer processing of the blur event until you know what comes next is with a delay. You can experiment with how much of a delay is required to make it work. I found that 100ms was not enough in Chrome, but 1000ms was enough. This is the kind of area where browsers tend to differ so you'd have to do a lot of cross browser testing. – jfriend00 Oct 01 '11 at 22:14
  • If you describe the whole problem you're trying to solve, there may be a better way to solve it that doesn't need this delay. – jfriend00 Oct 01 '11 at 22:15
  • 4
    Most things that involve timings are bad, in my experience. – Mark Redman Jun 04 '13 at 17:12
5

You can get this behavior by calling preventDefault() in the mousedown event of the control being clicked (that would otherwise take focus). For example:

btn.addEventListener('mousedown', function (event) {
  event.preventDefault()
})

btn.addEventListener('click', function(ev) {
    input.value += '@'
    input.setSelectionRange(ta.value.length, ta.value.length)
})

See live example here.

Scott Willeke
  • 8,884
  • 1
  • 40
  • 52
1

Some clarification that was too long to put in a comment.

The click event represents both pressing the mouse button down, AND releasing it on a particular element.

The blur event fires when an element loses focus, and an element can lose focus when the user "clicks" off of the element. But notice the behavior. An element gets blurred as soon as you press your mouse DOWN. You don't have to release.

That is the reason why blur gets fired before click.

A solution, depending on your circumstances, is to call preventDefault on mousedown and touchstart events. These events always (I can't find concrete documentation on this, but articles/SO posts/testing seem to confirm this) fire before blur.

This is the basis of Jens Jensen's answer.

V. Rubinetti
  • 1,324
  • 13
  • 21