39

I have a textarea where people enter some text (naturally), and I want to make it so that an AJAX request is made every now and then to get some suggestions regarding what the textarea is about (like stack overflow's Related Questions, but for a textarea, not a text input). The problem is that I can't do an AJAX request at every keypress (it'd be useless and very resource consuming), and I am not sure what would be the most efficient way to do it (every X words? every X seconds? or something else?).

What would be the best way to go about doing this?

Thank you in advance.

Trevor
  • 13,085
  • 13
  • 76
  • 99
Bruno De Barros
  • 1,535
  • 2
  • 16
  • 30
  • 1
    possible duplicate of [jQuery .keyup() delay](http://stackoverflow.com/questions/1909441/jquery-keyup-delay) – kenorb Mar 16 '15 at 13:56
  • @kenorb this question was actualy first, and you referenced was considered as a duplicate of this questions, but admins did not want to close the question because the title was better and it has about 132931 views which is quite remarkable. – Adam Jul 27 '16 at 18:57
  • It doesn't matter this is older, the other one has more votes and views, see: [Should I flag a question as duplicate if it has received better answers?](http://meta.stackoverflow.com/q/251938/55075). The title can be always edited to the better one. They can be always [merged](http://meta.stackexchange.com/a/147651/191655). – kenorb Jul 27 '16 at 19:03
  • Pretty cool to see my question from 2009 being reviewed in 2016. Talk about thorough moderation. – Bruno De Barros Jul 28 '16 at 08:55

7 Answers7

72

You could combine a keypress event handler with setTimeout so that you send an Ajax request a set amount of time after a keypress, cancelling and restarting the timer if another keypress occurs before the timer finishes. Assuming you have a textarea with id 'myTextArea' and an Ajax callback function called doAjaxStuff:

function addTextAreaCallback(textArea, callback, delay) {
    var timer = null;
    textArea.onkeypress = function() {
        if (timer) {
            window.clearTimeout(timer);
        }
        timer = window.setTimeout( function() {
            timer = null;
            callback();
        }, delay );
    };
    textArea = null;
}

addTextAreaCallback( document.getElementById("myTextArea"), doAjaxStuff, 1000 );
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 3
    This is the generally accepted "right" way to do this. Well said. – Gabriel Hurley Oct 25 '09 at 21:09
  • Thanks Tim, brilliant solution :D – Bruno De Barros Oct 27 '09 at 00:44
  • 3
    use onkeyup if you want to be able to capture backspace key events. – barkmadley Oct 27 '09 at 04:26
  • @barkmadley: that's fine if you don't want to be able to prevent its default action. You can capture backspace key events reliably in a keypress handler and prevent the default action. – Tim Down Oct 27 '09 at 09:16
  • @Tim Down: The code works well but has one problem: If you Alt-Tab (in Windows) to another application while the textarea has focus, and then Alt-Tab back again, the callback function is fired. It shouldn't, because no key has been pressed. I don't know how to solve that problem. – Gruber Dec 20 '12 at 14:21
22

What you're looking for is called debouncing. Here is a generic algorithm in native JavaScript:

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

And here's an example of how to use it with an onkeyup event:

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

const txt = document.querySelector('#txt')
const out = document.querySelector('#out')
const status = document.querySelector('#status')

const onReady = () => {
  txt.addEventListener('keydown', () => {
    out.classList.remove('idle')
    out.classList.add('typing')
    status.textContent = 'typing...'
  })
  
  txt.addEventListener('keyup', debounce(() => {
    out.classList.remove('typing')
    out.classList.add('idle')
    status.textContent = 'idle...'
  }, 800))
}

document.addEventListener('DOMContentLoaded', onReady)
#wrapper{
  width: 300px;
}

input{
  padding: 8px;
  font-size: 16px;
  width: 100%;
  box-sizing: border-box;
}

#out{
  margin: 10px 0;
  padding: 8px;
  width: 100%;
  box-sizing: border-box;
}

.typing{
  background: #A00;
  color: #FFF;
}

.idle{
  background: #0A0;
  color: #FFF;
}
<div id="wrapper">
  <input id="txt" placeholder="Type here" />
  <div id="out">Status: <span id="status">waiting...</span></div>
</div>
Trevor
  • 13,085
  • 13
  • 76
  • 99
14

Another alternative is to use a small jQuery plugin called bindWithDelay. It uses the same setTimeout technique that the accepted answer has, but handles the timeouts transparently so your code is a little easier to read. The source code can be seen on github.

$("#myTextArea").bindWithDelay("keypress", doAjaxStuff, 1000)
Brian Grinstead
  • 3,480
  • 5
  • 29
  • 23
4

If you are using lodash.js (or underscore.js) you can use the debounce function.

Example (jQuery used for keyup):

$('#myTextArea').keyup(_.debounce(function() {
  alert('hello');
}, 500));
Nick Russler
  • 4,608
  • 6
  • 51
  • 88
0

If you're interested in jQuery solutions, jQuery Suggest is a good implementation.

No sense reinventing the wheel every time.

Gabriel Hurley
  • 39,690
  • 13
  • 62
  • 88
  • This doesn't work for me, as I don't want to update the textarea or anything with the suggestions made. I just want to make an AJAX request to get the contents of another element in the page, every X keystrokes, or every X seconds the user is typing, or something. Just wondering what would be the most efficient option. – Bruno De Barros Oct 25 '09 at 11:22
0

I would personally use a setInterval that would monitor the textarea for changes and only perform AJAX callbacks when it detects a change is made. every second should be fast and slow enough.

barkmadley
  • 5,207
  • 1
  • 28
  • 31
  • 1
    This requires setInterval to run forever. Tim's solution is much more elegant, only running when someone is actually interacting with the textbox. – Gabriel Hurley Oct 25 '09 at 21:10
  • run forever until the page is unloaded. Also if you would like to provide some feedback as the user is typing my solution is better since it has the chance to send a request if the user is typing faster than one key per second. I'm not saying Tim's solution isn't elegant, but that in some cases it is not the solution depending on the application. – barkmadley Oct 26 '09 at 08:11
0

If you are using this functionality alot around the site and don't want to keep track of all of the setTimeout refs etc then i packaged this up into a plugin available here.

The plugin adds a few options to the normal $.ajax method for buffering and canceling previous ajax calls.

Question Mark
  • 3,557
  • 1
  • 25
  • 30