Home > Mobile >  setTimeout not working for keyup function
setTimeout not working for keyup function

Time:11-03

I have some code that fires a function on keyup inside an input field. To prevent this function from firing every literal keyup I set a timeout function of half a second so my code won't spam ajax requests.

But for some reason this timeout code isn't working.

This is what I had at first, which does work:

Form:

<form class="search-address">
  <input id="account_id" type="hidden" name="account_id" value="<?PHP echo $_SESSION['user']['id']; ?>">
  <input class="search-word" type="text" placeholder="Zoeken in adresboek...">
</form>

My trigger code:

$('.search-word').keyup(function(e) {
  clearTimeout($.data(this, 'timer'));
  if (e.keyCode == 13){
        search(true);
    }else{
        $(this).data('timer', setTimeout(search, 500));
    }
});

My function:

function search(force) {
  var zoekwaarde = $(".search-word").val();
    var account_id = $("#account_id").val();
    $.ajax({
     type:'post',
     url:"includes/searchaddress.php",
     data:({zoekwaarde: zoekwaarde, account_id: account_id}),
     success:function(data){
         $( "#adressenlist" ).show().empty().append( data );
         $( "#deleteresult" ).hide();
         $( "#adresresult" ).hide();
     }
 });
}

This works, but the problem is I have 2 forms with the same class of .search-word. So I tweaked my code a little like this:

$('.search-word').keyup(function(e) {
    searchword = $(this).val();
  clearTimeout($.data(this, 'timer'));
  if (e.keyCode == 13){
        search(true, searchword);
    }else{
        $(this).data('timer', setTimeout(search(true, searchword), 500));
    }
});

function search(force, searchword) {
  var zoekwaarde = searchword;
    var account_id = $("#account_id").val();
    $.ajax({
     type:'post',
     url:"includes/searchaddress.php",
     data:({zoekwaarde: zoekwaarde, account_id: account_id}),
     success:function(data){
         $( "#adressenlist" ).show().empty().append( data );
         $( "#deleteresult" ).hide();
         $( "#adresresult" ).hide();
     }
 });
}

I use searchword = $(this).val(); so I only post the value of $(this) input field, and not the last one in the dom tree. I thought this would work but this breaks my timeout function, it now triggers the search function instantly on every key press. What can I do to fix that?

CodePudding user response:

Try wrapping your search function in an anonymous function (callback) like:

setTimeout(() => { search(param1, param2) }, 1500)

CodePudding user response:

The OP needs to create a throttled version of the event handler.

E.g. underscore and lodash both provide a throttle method.

function handleSearch(/*evt*/) {
  console.log('handleSearch :: this.value ...', this.value);
}
document
  .forms[0]
  .querySelector('[type="search"]')
  .addEventListener('input', handleSearch);

const elmSearch = document
  .forms[1]
  .querySelector('[type="search"]');

// - with underscore's `throttle` the handler's `event` argument
//   unfortunately gets lost/ommitted.
// - a viable workaround is to firstly bind the to be handled control
//   to `handleSearch` and then to create a throttled handler from it. 
elmSearch.addEventListener('input', _.throttle(handleSearch.bind(elmSearch), 500));
[type=search] { width: 17em; margin: 10px 0; }
<script src="https://cdn.jsdelivr.net/npm/[email protected]/underscore-umd-min.js"></script>

<form class="search-address">
  <input type="search" placeholder="immediate addressbook search ...">
</form>

<form class="search-address">
  <input type="search" placeholder="throttled addressbook search ...">
</form>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Edit

"So... they should completely rewrite their code, removing jQuery, but adding underscore? Seems a bit much for such a simple problem. – Heretic Monkey"

"@HereticMonkey ... no one prevents them from writing their own throttle abstraction which I would advise anyhow. And the example above is a use case boiled down to the most necessary, in order to demonstrate the differences. The OP has a 2k reputation. I presume the OP can cope with that. – Peter Seliger"

Due to the arguments of Heretic Monkey there is a custom throttle implementation which takes complexity off the OP's event handling scripts. The OP's code then can be refactored to something like the following example ...

function search(force, searchword) {
  var zoekwaarde = searchword;
  var account_id = $("#account_id").val();
  $.ajax({
   type: 'post',
   url: "includes/searchaddress.php",
   data: ({zoekwaarde: zoekwaarde, account_id: account_id}),
   success: function(data){
     $( "#adressenlist" ).show().empty().append( data );
     $( "#deleteresult" ).hide();
     $( "#adresresult" ).hide();
   }
 });
}

function handleSearch(evt) {
  const searchword = evt.currentTarget.value;

  // search(true, searchword)
  console.log(`search(true, ${ searchword })`);
}
$('.search-word').each((_, elmNode) => {
  // each element of cause needs its own
  // (unique) throttled handler version.
  $(elmNode).on('input', throttle(handleSearch, 500));
});
body { margin : 0; }
[type=search] { width: 17em; margin-bottom: 3px; }
.as-console-wrapper { min-height: 70%!important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<form class="search-address">
  <input type="hidden" name="account_id" id="account_id" value="sessionUserId" />
  <input type="search" placeholder="Zoeken in adresboek ..." class="search-word" />
</form>

<form class="search-another-address">
  <input type="hidden" name="account_id" id="account_id" value="sessionUserId" />
  <input type="search" placeholder="Zoeken in adresboek ..." class="search-word" />
</form>

<script>
  function isFunction(type) {
    return (
         (typeof type === 'function')
      && (typeof type.call === 'function')
      && (typeof type.apply === 'function')
    );
  }
  function getSanitizedTarget(target) {
    return target ?? null;  
  }

  function getSanitizedInteger(value) {
    return (Number.isSafeInteger(value = parseInt(value, 10)) && value) || 0;
  }
  function getSanitizedPositiveInteger(value) {
    return Math.max(getSanitizedInteger(value), 0);
  }

  function throttle(proceed, threshold, isSuppressTrailingCall, target) {
    if (!isFunction(proceed)) {
      return proceed;
    }
    target = getSanitizedTarget(target);
    threshold = getSanitizedPositiveInteger(threshold) || 200;

    isSuppressTrailingCall = !!isSuppressTrailingCall;

    let timeoutId, timeGap;
    let timestampRecent, timestampCurrent;
    let context;

    function trigger(...argsArray) {
      timestampRecent = timestampCurrent;

      proceed.apply(context, argsArray);
    }
    function throttled(...argsArray) {
      clearTimeout(timeoutId);

      context = target ?? getSanitizedTarget(this);
      timestampCurrent = Date.now();

      if (timestampRecent) {
        timeGap = (timestampCurrent - timestampRecent);

        if (isSuppressTrailingCall && (timeGap >= threshold)) {
          // trailing call will be suppressed.

          trigger(...argsArray);
        } else {
          timeoutId = setTimeout(trigger, Math.max((threshold - timeGap), 0), ...argsArray);
        }
      } else {
        // initial call.

        trigger(...argsArray);
      }
    }
    return throttled;
  }
</script>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related