1252748
1252748

Reputation: 15372

adding click event to body when input is focussed on, then removing it on blur

I have a text input that I would like to, when it has focus, register a click event anywhere on the body. But when focus is removed from it, that click event is removed from the body. Sadly, I seem not to be able to suss it.

$(document).ready(function () {

    $("html").on("focus", "#asdf", function () {
        $("body").on("click", "*:not(#asdf)", wasItClicked);
    });

    $("html").on("blur", "#asdf", function () {
        $("body").off("click", "*", wasItClicked);
    });

});

function wasItClicked() {
    alert("yeah");
}

BIN

Thanks for any help.

Upvotes: 0

Views: 364

Answers (3)

Tom Pietrosanti
Tom Pietrosanti

Reputation: 4294

Ok, I see a couple of issues...

  1. You're delegating a focus event to the HTML element for an event on a single element... That's a bit of overkill, so I would put the focus and blur events directly on the input
  2. When you call off, you need to pass it the exact same selector in the second parameter
  3. Your click event is delegated to the body, and firing on any child element that is clicked and matches the selector - this does not include the body itself... not sure if you wanted it that way, but I moved it up to the html element, to include the body
  4. As soon as the input loses focus, the event will be removed, so the clicks won't register (You can use a timeout as @HMR suggested in their answer)

I had some problems with the delegation on the html element that was still returning the input (despite the :not(#asdf) selector) so I just put the filter into the function.

Here is the revised code (testing version):

var click_selector = ":not(#asdf)";
var click_target   = 'html';

$(document).ready(function () {

    $("#asdf").on("focus", function () {
        $(click_target).on("click", click_selector,  wasItClicked);
    });

    $("#asdf").on("blur", function () {
        // Use timeout to be able to register the click before function is removed
        // NOTE that since 'click' fires when the mouse is released, if they
        //   hold the mouse down for a while, the event will be gone and won't
        //   register.  Maybe better to use 'mousedown' instead of 'click'
        //   in which case the timeout could probably be reduced to 10ms or something

        // Also, using timeouts creates the possibility of multiple click handlers
        // present at the same time (new one added before the previous is removed)

        setTimeout( function(){ 
            $(click_target).off("click", click_selector, wasItClicked);
        }, 100);
    });

});

function wasItClicked(e) {
  e.stopPropagation();
  e.preventDefault();

  if( e.target.id !== 'asdf' ){
    console.log('yeah', click_target, click_selector);
  }
}

Upvotes: 0

Joe
Joe

Reputation: 28336

When #asdf is focused, and some other element is clicked, The events fire in order mousedown, blur, mouseup, click. So the handler has been removed before click fires.

The mousedown event fires before blur. If you are OK with mousedown instead of click, you could use this:

$(document).ready(function () {

    $("#asdf").on("focus", function () {
        $("body").on("mousedown", wasItClicked);
    });

    $("#asdf").on("blur", function () {
        $("body").off("mousedown", wasItClicked);
    });

});

(bin)

Edit:

You could use the mousedown event to help determine if you are losing focus because of a click, and remove the handler in the click handler if have lost focus.

$(document).ready(function () {
  $("#asdf").on("focus",function() {
    $("body").on("mousedown", setDown);
    $("body").on("click", wasItClicked);
   });
  $("#asdf").on("blur", function() {
    if ($(this).attr("mouse") != "down") {
     $("body").off("mousedown", setDown);
     $("body").off("click", wasItClicked);
    }
   });
});

function setDown() {
   $("#asdf").attr("mouse","down");
}

function wasItClicked() {
   if ($("#asdf") != $(document.activeElement)) {
     $("body").off("mousedown", setDown);
     $("body").off("click", wasItClicked);
   }
   $("#asdf").attr("mouse","up");
   alert("yeah");
}

new bin

Upvotes: 1

HMR
HMR

Reputation: 39270

You could use setTimeout to remove the click and use namespaces when adding and removing events because you may accidentally remove another click handler but the simplest way would be to remove the click event in the handler:

  ...
  $("body").on("click.fromasf", "*:not(#asdf)", wasItClicked);
  ...
  function wasItClicked() {
      $("body").off("click.fromasf");
      console.log("yeah");
  }

Here is an example using timeout:

<!DOCTYPE html>
<html>
 <head>
<script src="jquery-1.9.0.js"></script>
<style>
</style>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>Example</title>
</head>
 <body>
     <input type="text" id="asdf" />
     <input type="text" id="Text1" />
  <script>
      $(document).ready(function () {

          $("html").on("focus", "#asdf", function () {
              console.log("adding click handler");
              $("body").on("click.fromasf", "*:not(#asdf)", wasItClicked);
          });

          $("html").on("blur", "#asdf", function () {
              setTimeout(function () {
                  console.log("remove click");
                  $("body").off("click.fromasf");
              }, 500);
          });

      });

      function wasItClicked() {
          console.log("yeah");
      }
  </script>
 </body>
</html>

Upvotes: 0

Related Questions