Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent function from firing twice when bound to input on focus and click

Tags:

jquery

binding

I'm attempting to get an event to fire on both click and focus, however I only want it to fire once. When I click inside an input, it fires twice (clicking and focusing). How can I prevent this?

$('input').on('focus click', function(){
    console.log('fired');
});
like image 732
kylex Avatar asked Jul 08 '13 16:07

kylex


3 Answers

You can use .one instead. That will only allow the event to fire once, but will also remove the bind once it has been fired:

$('input').one('focus click', function(){
    console.log('fired');
});

If you need to keep the bind you will have to keep track of the state of the mouse button and the current target that triggered the mousedown:

var mouseDown, currentTarget;
$('input').on({
    "mousedown mouseup": function (e) {
        mouseDown = e.type === "mousedown";
        currentTarget = e.target;
    },
    "focus click": function (e) {
        if (mouseDown && currentTarget === e.target) return;
        console.log('fired');
    }
});

See test case on jsFiddle.

like image 135
mekwall Avatar answered Nov 17 '22 08:11

mekwall


A little hysteresis might be an option. Basically record the last time you responded to either event and ignore subsequent events within a guard time.

You could use jQuery's data for this (example at end of answer), but I prefer this: A general-purpose debouncer:

Live Example using click and focus | Live Source

$("#field").on("click focus", debounce(100, function(e) {
  // Event occurred, but not with 100ms of the previous one
}));

The debouncer function:

// debounce - debounces a function call
//
// Usage: var f = debounce([guardTime, ] func);
//
// Where `guardTime` is the interval during which to suppress
// repeated calls, and `func` in the function to call.
// You use the returned function instead of `func` to get
// debouncing;
//
// Example: Debouncing a jQuery `click` event so if it happens
// more than once within a second (1,000ms), subsequent ones
// are ignored:
//
//    $("selector").on("click", debounce(1000, function(e) {
//      // Click occurred, but not within 1000ms of previous
//    });
//
// Both `this` and arguments are passed through.
function debounce(guardTime, func) {
  var last = 0;

  if (typeof guardTime === "function") {
    func = guardTime;
    guardTime = 100;
  }
  if (!guardTime) {
    throw "No function given to debounce";
  }
  if (!func) {
    throw "No func given to debounce";
  }

  return function() {
    var now = +new Date();
    if (!last || (now - last) > guardTime) {
      last = now;
      return func.apply(this, arguments);
    }
  };
}

(The name "debouncer" is a common term for something that uses hysteresis to rate-limit inputs. IIRC, it comes from "switch debouncer" which is a (very) simple circuit used to avoid triggering an action hundreds of times as a mechanical-throw electric switch transitions from being open to being closed and vice-versa, since when the contacts get in proximity, there can be a lot of closed/open/closed/open/closed/open chatter before the switch hits steady-state. This chatter is called "bouncing," hence, "debouncer.")


The approach just using jQuery's data:

$('input').on('focus click', function(){
    var $this = $(this);
    var now = +new Date();
    var lastClicked = $this.data("lastClicked");
    if (lastClicked && (now - lastClicked) < 100) {
        // Don't do anything
        return;
    }
    $this.data("lastClicked", now);

    // Do the work
});
like image 8
T.J. Crowder Avatar answered Nov 17 '22 09:11

T.J. Crowder


This is an old question, but I couldn't find any other answers like what solved my problem. So I'm posting it here for anyone who happens to run into this problem in 2015.

$('#menu-button').on('click focus', function() {
  if(!$(this).is(':focus')) { // 1
    // Do stuff once
  }
  else {
    $this.blur(); // 2
  }
});
  1. This fires the event on click only. I'm not sure what's going on behind the scenes, so maybe someone can explain this to me, but tabbing and focus appear untouched and working 100%.

  2. This un-focuses the selected object, but it sets the focus path back to the top of the document. I leave this here so I can click the selected element again to disable a menu. I'm still looking for a fix to keep the focus path.

Edit: Better way:

$('#menu-button').on('click focus', function(event) {
  if(event.type === 'focus') { // 1
    // Do stuff once
  }
});
  1. Click will trigger a focus, but focus won't ever trigger a click. So just run code on the focus event.
like image 1
whaley Avatar answered Nov 17 '22 07:11

whaley