33

Here is some jQuery for a search box that I expect is actually an antipattern, and am sure there is a much better solution for that I would love to be pointed towards:

I will describe it in comments then just give the code, since the comments may be more clear and simple than the code:

  • // set up a function call on keypress.
  • // function call has a delay before the main event occurs.
  • // When keypress function is called, wipe any previously queued events and make a new one at the standard delay rate.
  • // Use a global to store the setTimeout pointer.
  • // clearTimeout any pre-existing pointers.
  • // Start a new delay.

The Code:

                // set up a filter function call on keypress.
                $('#supplier-select-filter').keypress(function (){
                    // Currently, resets a delay on an eventual filtering action every keypress.
                    filterSuppliers(.3, this);
                });

                // Delayed filter that kills all previous filter request.
                function filterSuppliers(delay, inputbox){
                    if(undefined != typeof(document.global_filter_trigger)){
                        clearTimeout(document.global_filter_trigger);
                        // clearTimeout any pre-existing pointers.
                    }
                    // Use a global to store the setTimeout pointer.
                    document.global_filter_trigger = setTimeout(function (){
                        var mask = $(inputbox).val();
                        $('#user_id').load("supplier.php?action=ajax_getsuppliers_html&mask="+escape(mask)); 
                    }, 1000*delay); 
                    // Finally, after delay is reached, display the filter results.             
                }

The problems:

On an input box where a search term may consist of 10 characters on average, that's 10 calls to setTimeout in a half a second, which seems to be processor heavy, and in my testing is causing some noticeable performance issues, so hopefully there's a cleaner alternative?

.load() is simpler than taking in JSON and then generating html from the json, but maybe there is a better tool?

.keypress() doesn't seem to always trigger on things like backspace deletion and other essentials, so perhaps using keypress() on this input box isn't the ideal?

Kzqai
  • 22,588
  • 25
  • 105
  • 137
  • Small comment – "10 calls to setTimeout in a half a second" is not really a performance issue, I wouldn't worry about it at all. – Joel L Feb 08 '10 at 06:37

3 Answers3

96

I frequently use the following approach, a simple function to execute a callback, after the user has stopped typing for a specified amount of time::

$(selector).keyup(function () {
  typewatch(function () {
    // executed only 500 ms after the last keyup event.
  }, 500);
});

Implementation:

var typewatch = (function(){
  var timer = 0;
  return function(callback, ms){
    clearTimeout (timer);
    timer = setTimeout(callback, ms);
  };
})();

I think this approach is very simple, and it doesn't imply any global variables.

For more sophisticated usages, give a look to the jQuery TypeWatch Plugin.

Christophe Roussy
  • 16,299
  • 4
  • 85
  • 85
Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • 10
    I also recommend checking if current_value !== previous_value in the callback function, before fetching new data via AJAX. Could be that the user is simply using the arrow keys, etc. – Joel L Feb 08 '10 at 06:39
  • Huh. Looks nice and clean, though I don't really get how the setTimeout pointer is able to be non-global yet still accessed later like a static var is in php, though I'm sure it works. What is it called that allows that? – Kzqai Feb 08 '10 at 22:58
  • 1
    @Tchalvak: The variable remains in-scope of the returned function even if the first function has finished its execution, because a **closure** is created, this is one of the most powerful features of JavaScript... Give a look to this article: http://www.jibbering.com/faq/faq_notes/closures.html – Christian C. Salvadó Feb 08 '10 at 23:21
  • 1
    I come back to this answer so much (essentially I want to use the typewatch on almost everything javascript) that I've gotta give a bounty here. – Kzqai Jun 24 '11 at 17:53
  • Just like CMS did, I do recommend flipping to keyup instead of keypress. There's a lot on SO that talks about it working a lot more cleanly across browsers for some reason. – Steph Rose Jun 28 '11 at 17:59
  • There is a jQuery event for text change of a textbox which is used like $(selector).on('input', callback); – anar khalilov Jul 13 '13 at 12:50
  • @CMS Tried this but for it did not keep the scope. I set the timer tp 5 seconds to clearly see what happend, then quickly changed values of several text boxes and only the last one changed worked. – Lars Thorén Sep 19 '14 at 08:52
3

Have a look at the excellent Text Change Event plugin from Zurb, purveyors of the highest quality jQuery goods.

Rob Cowie
  • 22,259
  • 6
  • 62
  • 56
  • The OP wanted a callback after a pause in typing, which is not what the Text Change Event plugin does. – Bennett McElwee Dec 06 '11 at 04:23
  • It _can_ help the OP implement the functionality he wants. It _does_ provide a better event on which to trigger the delayed callback. I'm not the only one on this page to suggest that the callback should probably not be called if the content of the input hasn't changed. – Rob Cowie Dec 06 '11 at 13:23
0

This is a more good way to do it, without using the plugin:

var timeout;
$('input[type=text]').keypress(function() {
if(timeout) {
    clearTimeout(timeout);
    timeout = null;
}

timeout = setTimeout(myFunction, 5000)
})
HarshSharma
  • 630
  • 3
  • 9
  • 34