91

I want to trigger an event just after I stop typing (not while typing) characters in my input textbox.

I've tried with:

$('input#username').keypress(function() {
    var _this = $(this); // copy of this object for further usage

    setTimeout(function() {
        $.post('/ajax/fetch', {
            type: 'username',
            value: _this.val()
        }, function(data) {
            if(!data.success) {
                // continue working
            } else {
                // throw an error
            }
        }, 'json');
    }, 3000);
});

But this example produces a timeout for every typed character and I get about 20 AJAX requests if I type-in 20 characters.

On this fiddle I demonstrate the same problem with a simple alert instead of an AJAX.

Is there a solution for this or I'm just using a bad approach for this?

Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
  • 1
    I am afraid that javascript doesn't provide an event that would allow you to be notified when the user stops typing into an input field. Why do you need that? – Darin Dimitrov Dec 26 '12 at 14:51
  • 3
    isn't it obvious from the example? I want to trigger an event "when the end-user stops typing in it", instead of sending 20 requests –  Dec 26 '12 at 14:52
  • 1
    There is no way to tell when the user is actually done typing unless they manually submit or change fields. How are you to know if the user pauses mid sentence and waits for 5 minutes before typing more? A possible solution would be to use .blur() and send when the users focus leaves the field. – Kevin M Dec 26 '12 at 14:53
  • 15
    The comments above are silly. This is a common use case: I want one event when the user is done resizing their window, zooming the map, dragging, typing... basically any continuous action on the user's part needs to be translated into our digital universe. Even a single keystroke suffers from this problem: when you strike a key, it actually "bounces", creating not just 1 keystroke event, but many. The hardware or OS of your computer removes these extra events, and that's why we have the illusion of discrete keystroke events. This is called "debouncing", and this is what the OP needs. – Ziggy Nov 06 '13 at 09:40
  • warning for react users: http://stackoverflow.com/a/28046731/57883 – Maslow Dec 08 '16 at 22:32
  • Does this answer your question? [Run javascript function when user finishes typing instead of on key up?](https://stackoverflow.com/questions/4220126/run-javascript-function-when-user-finishes-typing-instead-of-on-key-up) – Damjan Pavlica Apr 17 '20 at 17:38

13 Answers13

174

You'll have to use a setTimeout (like you are) but also store the reference so you can keep resetting the limit. Something like:

//
// $('#element').donetyping(callback[, timeout=1000])
// Fires callback when a user has finished typing. This is determined by the time elapsed
// since the last keystroke and timeout parameter or the blur event--whichever comes first.
//   @callback: function to be called when even triggers
//   @timeout:  (default=1000) timeout, in ms, to to wait before triggering event if not
//              caused by blur.
// Requires jQuery 1.7+
//
;(function($){
    $.fn.extend({
        donetyping: function(callback,timeout){
            timeout = timeout || 1e3; // 1 second default timeout
            var timeoutReference,
                doneTyping = function(el){
                    if (!timeoutReference) return;
                    timeoutReference = null;
                    callback.call(el);
                };
            return this.each(function(i,el){
                var $el = $(el);
                // Chrome Fix (Use keyup over keypress to detect backspace)
                // thank you @palerdot
                $el.is(':input') && $el.on('keyup keypress paste',function(e){
                    // This catches the backspace button in chrome, but also prevents
                    // the event from triggering too preemptively. Without this line,
                    // using tab/shift+tab will make the focused element fire the callback.
                    if (e.type=='keyup' && e.keyCode!=8) return;
                    
                    // Check if timeout has been set. If it has, "reset" the clock and
                    // start over again.
                    if (timeoutReference) clearTimeout(timeoutReference);
                    timeoutReference = setTimeout(function(){
                        // if we made it here, our timeout has elapsed. Fire the
                        // callback
                        doneTyping(el);
                    }, timeout);
                }).on('blur',function(){
                    // If we can, fire the event since we're leaving the field
                    doneTyping(el);
                });
            });
        }
    });
})(jQuery);

$('#example').donetyping(function(){
  $('#example-output').text('Event last fired @ ' + (new Date().toUTCString()));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<input type="text" id="example" />
<p id="example-output">Nothing yet</p>

That will execute when:

  1. The timeout has elapsed, or
  2. The user switched fields (blur event)

(Whichever comes first)

Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • 2
    @Brad: This jquery solution works fine except the backspace key typed in the input box is not detected in the latest chrome(38.0.2125.111). Only changing it to `keyup` works. You might want to inspect this and modify the code appropriately. – palerdot Oct 30 '14 at 09:21
  • @palerdot: Give this a shot and see if it resolves it. If so, I'll update my answer. ;-) http://jsfiddle.net/m894sfx5/ – Brad Christie Oct 30 '14 at 13:59
  • @BradChristie: This fiddle version works fine in the mentioned chrome browser. – palerdot Oct 30 '14 at 14:12
  • 3
    Thanks for this plugin - works great. I had to make one adjustment, because it wouldnt work when someone pasted something into the input field. I edited the following line to include `paste` -- `$el.is(':input') && $el.on('keyup keypress paste',function(e){` – Matt Dec 30 '14 at 18:29
  • why you use keyress? use just `keyup` event and delete `if (e.type=='keyup' && e.keyCode!=8) return;` it works fine on chrome and FF... – Matrix Feb 24 '15 at 11:45
  • Very useful, teeny tiny improvement for better context handling: This line `callback.call(el);` should be `callback.call(this, el);`. Makes the function more flexible if handlers are bound with `.bind`. A handler could look like this then: `function(el) { console.log(el, this); }` – ntaso Nov 12 '15 at 15:14
  • 1
    @Sangar82: Should now (though I'm a little confused, ctrl+v should be captured by keypress--unless you're right-click->pasting? – Brad Christie Jan 12 '16 at 21:01
  • Is there any way that we can use this event with "ON" like `$('#example').on('donetyping' ,function(){ $('#example-output').text('Event last fired @ ' + (new Date().toUTCString())); });` ?? It would be very helpful then. – Shivek Parmar Apr 13 '17 at 19:48
  • @shivek it's been wrapped as an extension, so there's no need to use on. – Brad Christie Apr 15 '17 at 12:24
  • 1
    @BradChristie Not working on Android Chrome in general or on desktop Chrome if text is deleted with DEL-Key – rwx Jun 05 '17 at 14:49
  • I don't understand the reason for the semicolon at the beginning of the code. Also, what is the context of this code? jQuery or something else? I mean, I know I'm looking at it 5 years later, but it seems relevant. – Ken Ingram Sep 21 '17 at 04:33
  • 1
    @KenIngram semicolon is in case it's minified with other modules it avoids invalid statements (fron incomplete previous scripts). As for context, it extends the jQuery library to add a `donetyping` extension method (similar to `.click` from previous iterations of jquery (before `.on(click', ...)` become the standard). Hope that helps. – Brad Christie Sep 22 '17 at 19:23
  • really awesome, in my case, I have appended the input field dynamically in jquery, so in that case above methods not working because DOM traverse problem, so i am going to use delegate function in jquery, this scenario how to use above your method??? @Brad Christie – Balakumar B Jun 13 '18 at 05:30
  • You can also add a minimum chars like this: `donetyping: function (callback, minChars, timeout) { timeout = timeout || 1e3; // 1 second default timeout minChars = minChars || 3; let timeoutReference, doneTyping = function (el) { if (!timeoutReference || el.value.length < minChars) return; timeoutReference = null; callback.call(el); };` – Eugen Zaharia Jun 14 '20 at 06:24
  • If one selects all the text and cuts the text using Ctrl + C. The event is never fired, how would you say on that? – Karue Benson Karue Oct 12 '20 at 23:21
81

SOLUTION:

Here is the solution. Executing a function after the user has stopped typing for a specified amount of time:

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

Usage

$('input').keyup(function() {
  delay(function(){
    alert('Hi, func called');
  }, 1000 );
});
Abram
  • 39,950
  • 26
  • 134
  • 184
Ata ul Mustafa
  • 1,172
  • 12
  • 18
  • Can someone explain to me how this works? Seems like Immediately Invoked Function Expression bit is the main thing here, but I don't think I understand what's happening. – NulisDefo Nov 05 '21 at 14:10
  • Wait a sec... Is this set up as IIFE just to set up the timer variable in a contained way (as opposed to separately outside the `delay` variable? And following that the rest is just a normal Function Expression? – NulisDefo Nov 05 '21 at 15:16
17

You can use underscore.js "debounce"

$('input#username').keypress( _.debounce( function(){<your ajax call here>}, 500 ) );

This means that your function call will execute after 500ms of pressing a key. But if you press another key (another keypress event is fired) before the 500ms, the previous function execution will be ignored (debounced) and the new one will execute after a fresh 500ms timer.

For extra info, using _.debounce(func,timer,true) would mean that the first function will execute and all other keypress events withing subsequent 500ms timers would be ignored.

ScorpionKing2k5
  • 2,491
  • 1
  • 18
  • 18
10

You need debounce!

Here is a jQuery plugin, and here is all you need to know about debounce. If you are coming here from Google and Underscore has found its way into the JSoup of your app, it has debounce baked right in!

Ziggy
  • 21,845
  • 28
  • 75
  • 104
10

You should assign setTimeout to a variable and use clearTimeout to clear it on keypress.

var timer = '';

$('input#username').keypress(function() {
  clearTimeout(timer);
  timer = setTimeout(function() {
    //Your code here
  }, 3000); //Waits for 3 seconds after last keypress to execute the above lines of code
});

Fiddle

Hope this helps.

Gibin Ealias
  • 2,751
  • 5
  • 21
  • 39
7

cleaned solution :

$.fn.donetyping = function(callback, delay){
  delay || (delay = 1000);
  var timeoutReference;
  var doneTyping = function(elt){
    if (!timeoutReference) return;
    timeoutReference = null;
    callback(elt);
  };

  this.each(function(){
    var self = $(this);
    self.on('keyup',function(){
      if(timeoutReference) clearTimeout(timeoutReference);
      timeoutReference = setTimeout(function(){
        doneTyping(self);
      }, delay);
    }).on('blur',function(){
      doneTyping(self);
    });
  });

  return this;
};
Matrix
  • 3,458
  • 6
  • 40
  • 76
3

There is some simple plugin I've made that does exacly that. It requires much less code than proposed solutions and it's very light (~0,6kb)

First you create Bid object than can be bumped anytime. Every bump will delay firing Bid callback for next given ammount of time.

var searchBid = new Bid(function(inputValue){
    //your action when user will stop writing for 200ms. 
    yourSpecialAction(inputValue);
}, 200); //we set delay time of every bump to 200ms

When Bid object is ready, we need to bump it somehow. Let's attach bumping to keyup event.

$("input").keyup(function(){
    searchBid.bump( $(this).val() ); //parameters passed to bump will be accessable in Bid callback
});

What happens here is:

Everytime user presses key, bid is 'delayed' (bumped) for next 200ms. If 200ms will pass without beeing 'bumped' again, callback will be fired.

Also, you've got 2 additional functions for stopping bid (if user pressed esc or clicked outside input for example) and for finishing and firing callback immediately (for example when user press enter key):

searchBid.stop();
searchBid.finish(valueToPass);
Adam Pietrasiak
  • 12,773
  • 9
  • 78
  • 91
1

I've been searching for a simple HTML/JS code and I did not found any. Then, I wrote the code below using onkeyup="DelayedSubmission()".

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt-br" lang="pt-br">
<head><title>Submit after typing finished</title>
<script language="javascript" type="text/javascript">
function DelayedSubmission() {
    var date = new Date();
    initial_time = date.getTime();
    if (typeof setInverval_Variable == 'undefined') {
            setInverval_Variable = setInterval(DelayedSubmission_Check, 50);
    } 
}
function DelayedSubmission_Check() {
    var date = new Date();
    check_time = date.getTime();
    var limit_ms=check_time-initial_time;
    if (limit_ms > 800) { //Change value in milliseconds
        alert("insert your function"); //Insert your function
        clearInterval(setInverval_Variable);
        delete setInverval_Variable;
    }
}

</script>
</head>
<body>

<input type="search" onkeyup="DelayedSubmission()" id="field_id" style="WIDTH: 100px; HEIGHT: 25px;" />

</body>
</html>
AbcAeffchen
  • 14,400
  • 15
  • 47
  • 66
1

We can use useDebouncedCallback to perform this task in react.

import { useDebouncedCallback } from 'use-debounce'; - install npm packge for same if not installed

const [searchText, setSearchText] = useState('');

const onSearchTextChange = value => {
    setSearchText(value);
  };

//call search api
  const [debouncedOnSearch] = useDebouncedCallback(searchIssues, 500);
  useEffect(() => {
    debouncedOnSearch(searchText);
  }, [searchText, debouncedOnSearch]);
Amol Aher
  • 189
  • 2
  • 4
0

why do that much when you just want to reset a clock ?

var clockResetIndex = 0 ;
// this is the input we are tracking
var tarGetInput = $('input#username');

tarGetInput.on( 'keyup keypress paste' , ()=>{
    // reset any privious clock:
    if (clockResetIndex !== 0) clearTimeout(clockResetIndex);

    // set a new clock ( timeout )
    clockResetIndex = setTimeout(() => {
        // your code goes here :
        console.log( new Date() , tarGetInput.val())
    }, 1000);
});

if you are working on wordpress , then you need to wrap all this code inside an jQuery block :

jQuery(document).ready(($) => {
    /**
     * @name 'navSearch' 
     * @version 1.0
     * Created on: 2018-08-28 17:59:31
     * GMT+0530 (India Standard Time)
     * @author : ...
     * @description ....
     */
        var clockResetIndex = 0 ;
        // this is the input we are tracking
        var tarGetInput = $('input#username');

        tarGetInput.on( 'keyup keypress paste' , ()=>{
            // reset any privious clock:
            if (clockResetIndex !== 0) clearTimeout(clockResetIndex);

            // set a new clock ( timeout )
            clockResetIndex = setTimeout(() => {
                // your code goes here :
                console.log( new Date() , tarGetInput.val())
            }, 1000);
        });
});
insCode
  • 1,227
  • 14
  • 10
  • if you want to extend jquery and want to use this method in multiple input elements, then the approved answer is the answer you are looking for . – insCode Aug 28 '18 at 15:52
0

This is what I am using with a formControl. It works for me.

this.form.controls[`text`].valueChanges
  .pipe(debounceTime(500), distinctUntilChanged())
  .subscribe((finalText) => {
    yourMethod(finalText);
});
oguzhancerit
  • 1,436
  • 1
  • 16
  • 27
-1

In my thinking a user stops writing when he doesn't keep focus on that input. For this you have a function called "blur" which does stuff like

stefanz
  • 1,316
  • 13
  • 23
  • 4
    wrong! blur is an event when the focus is lost on a particular input field. what I need to is determine when keyboard is not in usage under that field on which I'm trying to trigger an event. –  Dec 27 '12 at 08:16
-1

Use the attribute onkeyup="myFunction()" in the <input> of your html.

mujuonly
  • 11,370
  • 5
  • 45
  • 75