Combining multiple keyups into single event to reduce number of ajax requests - javascript

I have a keyup event on a search box that produces suggestions by fetching data from db. It is working fine. But the problem arises when i press keyboard buttons quickly and for every keyup event it gets me the result which produce duplicates.
I tried using
$("#search").on("keyup", function() {
setTimeout(getLocationFromDb, 1000);
});
But that still is producing same result (sending request to server for every keyup event).
I was looking for solutions but couldn't find one. Thanks for help.
EDIT
I am clearing the results beforeSend and appending the results in success.

Your approach isn't that bad - just make sure to clear all old timouts before setting a new one. You of course have to store the timeout somwhere - you could, for example, create a variable inside a closure.
This code only calls your callback when there was no new input for 1 second:
$("#search").on("keyup", (function () {
var timeout;
return function (e) {
window.clearTimeout(timeout);
timeout = window.setTimeout(getLocationFromDb, 1000);
}
})());
Fiddle: http://jsfiddle.net/uq4x9/

You can use boolean variable to detect if ajax call is finished already before send new
var isAjaxSent = false;
$("#search").on("keyup", function() {
isAjaxSent = true;
setTimeout(getLocationFromDb, 1000);
});
function getLocationFromDb( .... ) {
if (!isAjaxSent) {
//body of function
// in ajax success function set isAjaxSent to false
}
}

Related

Executing current ajax call and aborting all previous ones

I am trying to build an auto-complete UI. There is an input whose on keyup function does an ajax call to server to fetch the most relevant data. But if user types a word which is, say 10 character long, so for each keyup one ajax call is made and my dialogue box refreshes 10 times.
I have tried using abort() for the ajax call. When I do an abort to previous ajax call, the call is not made but still it waits for 10 calls before executing the last one, which makes the user experience very bad.
So is there a way to execute just the current ajax call without any delay from the previous ones?
A part of my code:
var request_autocomplete=jQuery.ajax({});
$('.review_autocomplete').keyup(function() {
request_autocomplete.abort();
request_autocomplete=jQuery.ajax({
// DO something
});
});
OP, there are two parts to this. The first is your abort, which it seems that you already have.
The second is to introduce forgiveness into the process. You want to fire when the user stops typing, and not on every key press.
You need to use both keyUp and keyDown. On keyUp, set a timeout to fire your submit. Give it perhaps 700ms. On KeyDown, clear the timeout.
var request_autocomplete=jQuery.ajax({});
var forgiveness;
// first your AJAX routine as a function
var myServiceCall = function() {
request_autocomplete.abort();
request_autocomplete=jQuery.ajax({
// DO something
}
// keyup
$('.review_autocomplete').keyup(function() {
forgiveness = window.setTimeout(myServiceCall, 700);
});
});
// key down
$('.review_autocomplete').keydown(function() {
window.clearTimeout(forgiveness);
});
});
What this will do is constantly set a timeout to fire every time a key is up, but each time a key is down it will cancel that timeout. This will have the effect of keeping your service call from firing until the user has stopped typing, or paused too long. The end result is that you will wind up aborting a much smaller percentage of your calls.
you can implement the way you asked in your question is preventing for example 3 calls as below :
var calls = 0;
$('.review_autocomplete').keyup(function() {
if (calls >3) {
request_autocomplete.abort();
request_autocomplete=jQuery.ajax({
// DO something
});
calls = 0;
}
calls++;
});
but this way not recommended because when user wants to types sample while user types samp at p ajax call fire up. and when user type l and e nothing happen !
If you are using jquery Autocomplete
you can using
minLenght so you can check current lenght of text box and when user typed at least 3 character then you must call the ajax request.
delay (between last keystroke and ajax call. Usually 2-300ms should do)
and using AjaxQueue
after a quick search about this issue I have found this link that shows another way to prevent multiple ajax calls for autocomplete by using cache
You could use a globalTimeout variable that you reset with setTimeout() and clearTimeout().
var globalTimeout;
$('.review_autocomplete').keydown(function(){
if(globalTimeout)clearTimeout(globalTimeout);
}).keyup(function(){
globalTimeout = setTimeoout(function(){
$.ajax({/* you know the drill */});
}, 10);
});
This way the timeout is cleared whenever your Client pushes a keydown, yet the timeout is set again as soon as the your Client releases a key onkeyup, therefore $.ajax() will only be called if there's no key action, after 10 milliseconds in this case. I admit that this won't stop an $.ajax() call that has already been made, however it probably won't matter because they happen pretty fast, and because this example prevents future $.ajax() calls as long as the Client keeps typing.
Try
var count = {
"start": 0,
// future , by margin of `count.timeout`
"complete": 0,
// if no `keyup` events occur ,
// within span of `count.timeout`
// `request_autocomplete()` is called
// approximately `2` seconds , below ,
// adjustable
"timeout" : 2
};
$('.review_autocomplete')
.focus()
.on("keyup", function (e) {
elem = $(this);
window.clearInterval(window.s);
window.s = null;
var time = function () {
var t = Math.round($.now() / 1000);
count.start = t;
count.complete = t + count.timeout;
};
time();
var request_autocomplete = function () {
return jQuery.ajax({
url: "/echo/json/",
type: "POST",
dataType: "json",
data: {
json: JSON.stringify({
"data": elem.val()
})
}
// DO something
}).done(function (data) {
window.clearInterval(s);
console.log("request complete", data);
$("body").append("<br /><em>" + data.data + "</em>");
elem.val("");
count.start = count.complete = 0;
console.log(count.start, count.complete);
});
};
window.s = setInterval(function () {
if (Math.round($.now() / 1000) > count.complete) {
request_autocomplete();
console.log("requesting data");
};
// increased to `1000` from `501`
}, 1000);
});
jsfiddle http://jsfiddle.net/guest271314/73yrndwy/

Is it a bad idea to trigger a database lookup on keyup (i.e. checking for unique username)

I have a field where a user can manually enter a code for a new product. It cannot be a db-generated ID as the user needs control over the code.
As this will require a round-trip to the server to check, my intention was to trigger this check as the "keyup" event happens (a bit like a live filter would work) so that there is instantaneous feedback about the entered code (and probably CSS-based colour/image reinforcement). Admittedly for this particular function I may choose to do it onblur instead of keyup but there may well be other situations where I require a check on keyup.
Is this fundamentally a bad thing or not? A round-trip to the server to check if an item code exists (primary key) should be a very fast process but with a slow connection I'm just wondering if there could be a race condition whereby a fast typer stacks up the db calls faster than it can return and then a situation may emerge whereby a "submit" button is enabled when it shouldn't be. Of course I would also back this up with db-level checking on submission of the form, but I'm trying to make this as apparently responsive as possible.
Is it generally a bad idea to do keyup checking on any data source that's not held in memory or is this an acceptable practise?
Since you use knockout you can use throttle
ViewModel = function() {
this.text = ko.observable().extend({ throttle: 500 });
this.text.subscribe(this.onText, this);
};
ViewModel.prototype = {
onText: function(value) {
console.log("ajax call");
}
};
http://jsfiddle.net/TT3AB/
There's never going to be a race condition if you abort any previous ajax request on the next keyup.
EDIT
(function() {
var xhr; //a reference to an XMLHttpRequest Object
var onKeyUpCallback = function() {
if(xhr) {
xhr.abort();
}
xhr = ... //build your XHR object
//....
xhr.send();
}
yourInputElement.addEventListener('keyup',onkeyUpCallback);
}());
If you want to check at real time, you can approach in this way. When user types data and halts for a while then send you ajax request to check for existence of that particular thing in database.
Call a function on key up event but do not send request until user stops for a while
var timer;
function onkeyups()
{
if(timer)
{
clearTimeout(timer);
}
timer = setTimeout(function(){callRequest();},500) // the delay, after how much millisecond the ajax call should be made when user has stopped typing
}
function callRequest()
{
// Make your ajax call or whatever
}

Multiple change events

I have 3 <select> menus, each with change events working on them. Break down of the code looks like this:
$(document).ready(function(){
// some code
$("#selectOne").change(function() {
// some code inc ajax request
$("#selectTwo").change(function() {
// some code inc a different ajax request
});
$("#selectThree").change(function() {
// some code inc a yet another ajax request
});
});
});
The problem with the above is that, while selectOne works fine, and selectTwo seems to work fine also, if I change selectThree, the code for both selectTwo AND selectThree fires at the same time. Depending on the sequence of selection of any of the 3 selects, the response of selectThree.change can be to display and hide each of the previous responses, before settling on displaying an incorrect response.
What I'd like to do is this:
$(document).ready(function(){
// some code
$("#selectOne").change(function() {
// some code inc ajax request
});
$("#selectTwo").change(function() {
// some code inc a different ajax request
});
$("#selectThree").change(function() {
// some code inc a yet another ajax request
});
});
In this scenario, selectOne works fine, but selectTwo and selectThree don't respond to change.
Is there a way of correcting any of this, so that as each element is changed, only the correct change event is fired?
It seems that when your document is loaded there is no select elements with id "selectTwo" or "selectThree". Similarly in the first case, there is no "selectThree" when the eventhandler of "selectOne" executes. That's why the corresponding event handlers does not execute. There is two way you can handle this proplem.
1 - Assing handlers when the elements are created.
2 - Use .on() instead of .change():
$('body').on("change", "#selectOne", function () {});
$('body').on("change", "#selectTwo", function () {});
$('body').on("change", "#selectThree", function () {});

jQuery / backbone.js - delay function call

I have a #search element, which when the keyup event occurs should fire a function. This function should only fire if keyup hasn't occurred in a set amount of time (say 500 milliseconds for example). This will prevent search results from updating every letter that is pressed. The problem is that with backbone.js, I have my events in a hash and the one that is applicable looks like:
'keyup #search' : 'setSearch'
which calls the setSearch() function when the keyup event occurs. I'm not really clear on how to handle it at this point. I've tried a variety of things, but nothing can maintain the timer past the function ending.
I have something like so:
setSearch: function(event) {
var timer = window.setTimeout( function() {
// run function here
alert('fired');
}, 500);
},
rather than the alert('fired'), I'll have my own function run. I can see why this code doesn't work (a timer is set for every keyup event that occurs. But I still don't have a clear idea on what else I could try.
What you are looking for is actually a function provided to you from underscore.js (a requirement of Backbone)
setSearch: _.throttle(function() {
//Do Stuff
}, 500),
In a nutshell, this returns a new form of the anonymous function that can only be called once every 500ms. You will likely have to tweak the timing to your needs.
More Info:
http://documentcloud.github.com/underscore/#throttle
You need an instance variable in your view that stores the timer ID, then you can stop it and restart it as needed:
setSearch: function(event) {
var self = this;
if(self.timer)
clearTimeout(self.timer);
self.timer = setTimeout(function() {
alert('fired');
self.timer = null;
}, 500);
}
So, if the timer is already running, you call clearTimeout to stop it, start a new timer, and store the timer ID in self.timer (AKA this.timer). You'll also want to reset the stored timer ID in the timer's callback function or your setSearch won't do anything after its timer has fired once. And all the self business is just to capture this for use in the timer's callback function.
Preventing the updating of search results on every keyup is exactly the kind of situation that Underscore's _.debounce(function, wait) function is meant to deal with. The underscore documentation for _.debounce() states:
Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed since the last time it was invoked. Useful for implementing behavior that should only happen after the input has stopped arriving.
Your refactored code would look as simple as:
setSearch: function(event) {
_.debounce(doSomething, 300);
},
Since you want your event handler events to be able to maintain whether or not an event has recentlyFired, you probably want to wrap your handler into a closure and maintain that status. The status should be changed to true when an event has fired, and reset to false after a delay of 500ms.
setSearch: function( ) {
var firedRecently = false;
return function(event) {
if (firedRecently) {
// it has fired recently. Do you want to do something here?
} else {
// not fired recently
firedRecently = true;
// run your function here
alert('fired');
var resetStatus = window.setTimeout( function () {
firedRecently = false;
}, 500);
}
}
}( );

Javascript: Do processing when user has stopped typing

I have a text box on a web page, whose value I want to send to a XMLHttpRequest. Now I want the user to just type the value, without pressing a button. But If i just send the request int he keyboard events, it will fire every time a key is pressed.
So basically I want something liek this
function KeyUpEvent()
{
if (user is still typing)
return;
else
//do processing
}
It would be great if the solution could come from plain javascript or mootools. I dont want to use any other library.
The way this is usually done is by restarting a timer on the keyup event. Something like this:
var keyupTimer;
function keyUpEvent(){
clearTimeout(keyupTimer);
keyupTimer = setTimeout(sendInput,1000); // will activate when the user has stopped typing for 1 second
}
function sendInput(){
alert("Do AJAX request");
}
Basically, you want to start a timer on KeyUp, and when KeyUp starts again, reset the timer. When the user stops typing, the timer runs out, and your request can go at that point.
Example:
var timout_id;
function keyup_handler(event) {
if (timout_id) {
clearTimeout(timout_id);
}
timout_id = setTimeout(function(){
alert('sending data: \n' + event.target.value)
}, 800);
}
Just attach the function to the input using your preferred method, and replace the alert with your preferred action.
Of course there are many ways you could generalize this approach and make it more reusable, etc, but I think this illustrates the basic idea.
I always use this simple function to handle a timer, that will fire a callback function, after the user has stopped typing for a specified amount of time:
var typewatch = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
}
})();
Usage (example with MooTools):
$('textInput').addEvent('keyup', function(e){
typewatch(function () {
// executed only 500 ms after the last keyup event
// make Ajax request
}, 500);
});
The main difference between this solution and solutions from other answers is that all the timer logic is handled by the typewatch function itself, the event handler doesn't need to know anything about the timer, it just invokes the function. Also, there are no global variables to take care (the timer id is not stored on a global variable).
You never know when a user is really "finished" typing. The user might take a sneeze break, or a stretch break, or a coffee break, and then continue typing.
However, if you're implementing something like an autocomplete mechanism, you can set a timer (cf. window.setTimeout(...)) to see if the user hasn't typed anything in a certain amount of time. If you get another key-up event while the timer is running, you can start the timer over.
var keyTimer;
function onKeyUp(){
clearTimeout(keyTimer);
setTimeout(stoppedTyping,1500);
}
function stoppedTyping(){
// Profit! $$$
}
EDIT: Damn ninjas
I wrote a custom jQuery event because I use this logic a lot:
jQuery.event.special.stoppedtyping = {
setup: function(data, namespaces) {
jQuery(this).bind('keyup', jQuery.event.special.stoppedtyping.keyuphandler);
},
teardown: function(namespaces) {
jQuery(this).bind('keyup', jQuery.event.special.stoppedtyping.keyuphandler);
},
keyuphandler: function(e) {
var interval = 1000;
var el = this;
if (jQuery.data(this, 'checklastkeypress') != null) {
clearTimeout(jQuery.data(this, 'checklastkeypress'));
}
var id = setTimeout(function() {
jQuery(el).trigger('stoppedtyping');
}, interval);
jQuery.data(this, 'checklastkeypress', id);
}
};
You can use it like this:
$('input.title').bind('stoppedtyping', function() {
// run some ajax save operation
});
For some reason I could never get it to work with .live( ... ). I'm not sure why...
Use onBlur and maybe an onKeyDown to check for the user pressing the return/enter key.

Categories