Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debounce for 500ms if True else execute

Spoiler; I'm completely new to jQuery/Javascript.

I have a boolean field CheckMe and an input field textField.

If textField is empty, CheckMe should not be shown else it should (this means if goes from not-empty to empty CheckMe should be hidden right away again). I want to parse a delay, say 500 ms, i.e CheckMe is shown if text is not empty and after 500 ms of the last keypress

I have tried using the debounce function from this SO answer (see my implementation below), but the problem is, CheckMe is also first hidden after 500 ms of textField being empty

<script type="text/javascript">
  function debounce(fn, duration) {
  var timer;
  return function(){
    clearTimeout(timer);
    timer = setTimeout(fn, duration);
  }
}

$(document).ready(function () {
    const textField= $("#textField");
    const CheckMe= $("#CheckMe");
    CheckMe.hide();

    textField.on("input", debounce(()=> {
      if (textField.val()) {
        CheckMe.show();
      } else {
        CheckMe.hide();
      }
    },500));
});
</script>

but that removes checkMe after 500 ms when I clear textField.

I have tried moving the debounce into the True statement i.e

...
   textField.on("input", function() {
      if (textField.val()) {
        debounce(function(){CheckMe.show();},500)
      
      } else {

        CheckMe.hide();
      }
}

but that does not show CheckMe at any point

like image 337
CutePoison Avatar asked Nov 07 '22 02:11

CutePoison


1 Answers

The reason the attempt with if () { debouce(()={}); } else { immediate(); } doesn't work is due to how event handlers store the function and how debounce stores its timer.

When you run .on("input", function() { }) that function definition is stored within the event handler, ready to run. However with debounce, that's not what is being done, instead it's:

.on("input", function())

there's no function definition - it calls the function itself, which happens to return another function to be called when the event runs.

It's why there there are so many questions on SO saying something like "my code runs immediately when I do .on("input", myfunction()) when it should be .on("input", myfunction)

So that one function (the debounce) runs once per event setup - not once per input event fire, but just once when setting up the event. So there's only one instance of var timer and it's all contained within the debounce function. The event fire then calls the code inside the return function() which already has var timer defined previously in its outer scope (the previous debounce call).

If you call debounce again with a 2nd input $("#secondInp").on("input", debounce(() => ... you get a second instance of the function with its own variable (so they don't conflict between inp1 and inp2).

So you can then see that if you put this inside the event handler (in the if), you're calling a new debounce each time (not the same one).

Your attempt did nothing because your code debounce(function(){CheckMe.show();},500) simply returns the function - so you would need to do

debounce(function(){CheckMe.show();},500)();`

but even that won't work as each time it's called (each event) you get a new instance of the debounce function and a new instance of var timer.


You can use two events. Inside each event check if the "check me" should be shown or not.

The debounced one will run after 500ms and the not-debounced one will run immediately.

function debounce(fn, duration) {
  var timer;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(fn, duration);
  }
}


$(document).ready(function() {

  const textField = $("#textField");
  const checkMe = $("#checkMe");
  checkMe.hide();

  textField.on("input", debounce(() => {
    if (textField.val()) {
      checkMe.show();
    }
  }, 500));

  textField.on("input", () => {
    if (!textField.val()) {
      checkMe.hide();
    }
  });
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>

is it possible to keep it in a single if-else

Given the explanation above, it is not possible using your debounce function as it is; because of the return function() { } and the single instance of timer. The key being the single instance of timer.

Without the debounce, this can be implemented in a single function, using an outer variable for the timer, but will need the debounce code repeated each time (eg for a 2nd input) - just the clearTimeout and setTimeout code - so not much - it's the "global"/outer-scope variable that becomes an issue.

$(document).ready(function() {

  var textFieldTimer = null;
  
  const textField = $("#textField");
  const checkMe = $("#checkMe");
  checkMe.hide();

  textField.on("input", () => {
    if (textField.val()) {
      clearTimeout(textFieldTimer);
      textFieldTimer = setTimeout(() => checkMe.show(), 500);
    }
    else {
      checkMe.hide();
    }
  });
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>

So how can we use both: a reusable function and inside an if inside the event handler?

By storing the single instance of timer on the element itself - using .data() in the code below, but any method to store a single instance per element will also work.

Here's an example, using a single event with an if and repeated for a second input to show how it might work.

function debounce2(fn, duration)
{
    var timer = $(this).data("timer");
    clearTimeout(timer);
    $(this).data("timer", setTimeout(fn, duration));
}

$(document).ready(function() {

  $("#checkMe, #checkMe2").hide();

  $("#textField, #textField2").on("input", function() {
    if ($(this).val()) {
      debounce2.call(textField, () => $("#checkMe").show(), 500);
    }
    else {
      $("#checkMe").hide();
    }
  });



  // second input confirming `timer` is per element.
  
  $("#textField2").on("input", function() {
    if ($(this).val()) {
      debounce2.call(textField, () => $("#checkMe2").show(), 500);
    }
    else {
      $("#checkMe2").hide();
    }
  });  
});
#checkMe, #checkMe2 { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
<hr/>
<input id="textField2" type="text">
<div id='checkMe2'>^-- check this</div>
like image 171
freedomn-m Avatar answered Nov 15 '22 06:11

freedomn-m