I'm trying to add the JavaScript function setTimeout() to my jQuery expression. Without the setTimeout() my function loads a partial view with updated information when I enter text into my textbox. But, it loads the information with every keystroke. My logic is that if I can put in a timer then when the user stops typing, a second later the data is updated.
As you can see I tried to put a setTimeout() in but it doesn't seem to be working.
$(function() {
$('#DocId').live('keyup', function() {
setTimeout(var styleValue = $(this).val();
$('#tableContent').load(
'/CurReport/TableResults',
{ style: $(this).val()}), 1000);
}),
});
Thank you,
Aaron
You can use .data() and put in a delay timer that resets every keystroke like this:
$('#DocId').live('keyup', function() {
clearTimeout($.data(this, 'timer'));
var val = $(this).val();
var wait = setTimeout(function() {
$('#tableContent').load('/CurReport/TableResults', { style: val });
}, 1000);
$(this).data('timer', wait);
});
This sets a timeout of 1 second every keystroke on the element that it's typed it (can have any number of these going at once). Each keystroke clears the previous timer and sets a new one, so only after 1 complete second of not typing will the function fire.
Note: don't remove the var val = $(this).val(); portion, you need the variable because this is a different context when the function actually executes.
Related
I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});
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);
}
}
}( );
I need to use this plugin: http://jstepper.emkay.dk/Default.aspx to be able to use "mousewheel" value incrementation.
This plugin offers a function onStep, which will run a my own function each time a value is incremented. Now it would be real simple to just send new value to the ajax function, but that would mean hundreds, if not thousands of POST requests, lots of mysql inserts/updates, it would be very messy...
I am trying to implement somekind of a timer that would take the value from the textfield, wait 1000milliseconds and then post it to the server, whenever a user should scroll for more values, it would cancel the previous timeout and start counting again.
This introduced two problems, first - if a user scrolls one textfield and then immediately starts scrolling the next textfield the previous textfield data wouldn't be sent, because the new field would take the control of the timer.
And secondly, I couldn't get the values anyway I tried, this is probably easy to fix, I just have to figure out how to...
So I'm hoping that someone might help me in this, because I am stomped.
$(".basket_input").jStepper({onStep:function(){ajaxSleeper(this.value, this.id)}});
var timer = 0;
function ajaxSleeper(val1,val2){
clearTimeout(timer);
timer = setTimeout(function(){
ajaxUpdate(val1,val2);
//alert('updated!');
}, 3000);
}
HTML
<td><input class="basket_input" size="5" maxlength="6" type="text" value="" name="1939" id="1939" /></td>
The easiest thing to do would be to use $.data to set the timer and keep using the context of the actual object.
jQuery
var $basket_input = $(".basket_input");
$basket_input.jStepper({
onStep: function() {
ajaxSleeper.call($basket_input.get(0)); // Call the function using the
// element as the context
}
});
function ajaxSleeper() {
// Get any timer that could be set for this element
var thisTimer = $(this).data('timer');
// Clear it if there is one
if (thisTimer) clearTimeout(thisTimer);
// We need this as the context in setTimeout will change to "window" object
// Yet we want to reference this object in the setTimeout
var $that = $(this);
$(this).data('timer', setTimeout(function() {
ajaxUpdate($that.val(), $that.attr('id'));
//alert('updated!');
}, 3000);
);
}
Two possible solutions.
If you haven't seen Underscore.js, you should. They have a _.throttle() that will do exactly what you want, along with a million other useful things.
If you don't want to use another library, you're close.
var timer;
var fireOffMyRequest = function()
{
// ... FIRE EVERYTHING! ...
};
var somethingThatGetsFiredOften = function()
{
clearTimeout(timer);
timer = setTimeout(fireOffMyRequest, 4000);
};
That limits to 4 seconds; you could change the 4000 to reflect whatever you want the throttle limit to be.
edit: I forgot to address your timer issue. Store off the timer onto the HTML element:
$(this).data('timer', timer);
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.
I'm using YUI as javascript framework, and can successfully react when the user changes the value of basic input fields, the reaction being to sent an Ajax query.
However, I'm not so lucky with multiselect dropdown lists:
listening to "change" would send my query each time the user adds/removes an item to his selection
listening to "blur" requires the user to click elsewhere in order to loose the focus and send the query (not very usable), plus it would send the query if the user only scrolls on the list without changing anything (useless, confusing).
Any idea (with YUI), that would use a clever behavior?
Or should I really listen to change and implement a timeout (to wait for subsequent changes before sending a query)?
I use the same kind of timeout you want on key events, to detect when the user have finished typing, the same approach can be used on your problem:
// helper function
var timeout = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
Usage:
// YUI 2
YAHOO.util.Event.addListener(oElement, "change", function () {
timeout(function () {
// one second since the last selection change
}, 1000);
});
// YUI 3
Y.on("click", function () {
timeout(function () {
// one second since the last selection change
}, 1000);
}, oElement);
Basically in this timeout function, I reset the timer if the function is called before the specified delay.
you could run a setTimeout after the onChange event and keep track of a number of changes to determine whether or not a change had been made since the event was fired. if no changes were made within that time, then the query could be sent.
e.g., something like:
var changes = 0;
function myOnChangeHandler(e)
{
changes++;
var local_change = changes;
setTimeout(function() {
if (local_change === changes) {
sendRequest();
}
}, 500);
}