0
\$\begingroup\$

In my web application, there is a search input field, and when the user empties all of the text inside the input field, there is an API call made for a default-list of results, which are displayed in a dropdown right underneath the input field.

I'm using the keyup event to try to capture when the user will have completed deleting text, but I've not been able to re-create the desired behavior without using setTimeout(). I've been told to use a throttle or debounce method (no third party modules or plugins, although plain jQuery is ok).

Could someone please give me some advice on how to recreate this code without using setTimeout? I've been on this for a couple days no with no luck, any help is appreciated!

(function () {
  var desktopInput = document.getElementById('someId');
  var desktopResults = document.getElementsByClassName('someClass')[0];

  function getSuggestedSearchResults(cb) {
    $.ajax({
      url: url,
      data: {},
      timeout: REQ_TIMEOUT,
      xhrFields: {
        withCredentials: true
      },
      success: function (data) {
        cb(data);
      }
    });
  }

  function handleEmptyInputDesktop() {
    if (desktopInput.value === '') {
      setTimeout(function () {
        getSuggestedSearchResults(function (data) {
          var model = transformResults(data);
          var results = nunjucks.render('_fullscreenSearchResults', {
            results: model,
            source: 'suggested'
          });
          desktopResults.innerHTML = results;
        });
      }, 500);
    }
  }

  function init() {
    desktopInput.addEventListener('keyup', handleEmptyInputDesktop);
  }

  init();
})();

Without the setTimeout(), the dropdown menu will disappear if the key isn't pressed long enough on the first stroke that deletes all the text.

I've omitted some extraneous code from this snippet, but I'm happy to give more details if you like.

\$\endgroup\$
1

2 Answers 2

0
\$\begingroup\$

I ended up using window.requestAnimationFrame and it worked really well, just thought I'd share my solution based on the linked article.

 function requestAnimationTimeout(callback, delay) {
    var dateNow = Date.now;
    var requestAnimation = window.requestAnimationFrame;
    var start = dateNow();
    var stop;

    var timeoutFunc = function () {
      if (dateNow() - start < delay) {
       return stop || requestAnimation(timeoutFunc);
      } else {
       return callback();
      }
    };

    requestAnimation(timeoutFunc);

  }

And then simply use requestAnimationTimeout in place of the native setTimeout.

\$\endgroup\$
0
\$\begingroup\$

This is a great alternative to timeouts. I've added the clearTimeout for completeness.

function requestTimeout(callback, delay) {
    var tickTimeout = window.requestAnimationFrame, dateNow = Date.now,

        start   = dateNow(),
        stop    = false,

        timeout = function () {
            if (dateNow() - start < delay) {
                return stop || tickTimeout( timeout );
            } else {
                return callback();
            }
        };

    tickTimeout( timeout );

    return {
        clearTimeout: function () {
            stop = true
        }
    };
}

Here it has seamlessly replaced setTimeout in a debounce method.

$.extend({
    debounce: function (fn, timeout, invokeAsap, ctx) {
        var timer;

        if (arguments.length == 3 && typeof invokeAsap != 'boolean') {
            ctx = invokeAsap, invokeAsap = false;
        }

        return function () {
            var args = arguments;

            ctx = ctx || this;

            invokeAsap && !timer && fn.apply(ctx, args);

            timer && timer.clearTimeout();

            timer = requestTimeout(function () {
                !invokeAsap && fn.apply(ctx, args);
                timer = null;
            }, timeout);
        };
    }
});

Here is an example:

// The Target will fadeout 3 seconds after the 
// last time it has been clicked.

const fadeTargetDebounce = $.debounce(function () {
    $(this).fadeOut();
}, 3000);


$('button').on('click', function () {
    $(this).show( fadeTargetDebounce );
});
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Can you add an explanation for This is a great alternative to timeouts. I've added the clearTimeout for completeness. Answers on Code Review should review the code the poster wrote, this mostly seems an alternate solution but I could be wrong. \$\endgroup\$
    – pacmaninbw
    Jun 29, 2020 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.