How to respond to a Javascript event only if it is fired once and then not fired again during some time period?
In my application I listen to the Google Maps API 'bou开发者_C百科nds_changed' event to send an ajax request to update some div on the web page depending on the new boundaries of the map:
google.maps.event.addListener(map, 'bounds_changed', function() {
// here goes an ajax call
}
The event 'bounds_changed' is fired with a high frequency when the user drag the map around. So much that there are too many ajax requests sent to the server.
Basically I would like to make the ajax call only after the user has stopped to move the map during some time period (for example 500ms). I am not very experienced with Javascript and tried to achieve this with setTimeout and clearTimeout but without success.
Any idea would be appreciated :)
Add a timeout, that runs your code 500ms after the event fires, each time the event fires clear the timeout and create a new one.
eg.
google.maps.event.addListener(map, 'bounds_changed', (function () {
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(function() {
// here goes an ajax call
}, 500);
}
}()));
There is a really good approach available on unscriptable.com:
Function.prototype.debounce = function (threshold, execAsap) {
var func = this, // reference to original function
timeout; // handle to setTimeout async task (detection period)
// return the new debounced function which executes the original function
// only once until the detection period expires
return function debounced () {
var obj = this, // reference to original context object
args = arguments; // arguments at execution time
// this is the detection function. it will be executed if/when the
// threshold expires
function delayed () {
// if we're executing at the end of the detection period
if (!execAsap)
func.apply(obj, args); // execute now
// clear timeout handle
timeout = null;
};
// stop any current detection period
if (timeout)
clearTimeout(timeout);
// otherwise, if we're not already waiting and we're executing at the
// beginning of the waiting period
else if (execAsap)
func.apply(obj, args); // execute now
// reset the waiting period
timeout = setTimeout(delayed, threshold || 100);
};
}
This would let you do:
// call the function 200ms after the bounds_changed event last fired:
google.maps.event.addListener(map, 'bounds_changed', (function() {
// here goes an ajax call
}).debounce(200));
// call the function only once per 200ms:
google.maps.event.addListener(map, 'bounds_changed', (function() {
// here goes an ajax call
}).debounce(200,true));
If you prefer to not augment the Function.prototype
there is a standalone function debounce(func, threshold, execAsap)
available on the blog post.
google suggests using another listener ...
google.maps.event.addListener(map, 'idle', showMarkers);
quote "Note that you could listen to the bounds_changed event but it fires continuously as the user pans; instead, the idle will fire once the user has stopped panning/zooming." /quote
see
http://code.google.com/apis/maps/articles/toomanymarkers.html#gridbasedclustering
This code will ensure it has been half a second since the event was last fired before doing its thing (the commented TODO
). I think this is what you want.
var mapMoveTimer;
google.maps.event.addListener(map, 'bounds_changed', function(){
clearTimeout(mapMoveTimer);
mapMoveTimer = setTimeout(function(){
// TODO: stuff with map
}, 500);
});
This is Brenton Alker code but moved into a utility function.
var frequencyReduce = function(delay, callback){
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(callback, delay);
};
};
google.maps.event.addListener(map, 'bounds_changed', frequencyReduce(500, function(){
// here goes an ajax call
}));
One quick and dirty solution would be to call the server less frequently:
var requestCounter = 0;
var frequency = 50;
google.maps.event.addListener(map, 'bounds_changed', function() {
if((++requestCounter % frequency) == 0){
// here goes an ajax call (also, optionally reset the counter here)
}
});
Alternatively, I've done something similar where I've reset a timer every time I "heard from" the user. Once the timer expired, it called my action. So the timer continuously tries to go off but if the user does something, the timer is reset. Eventually, the user stops moving long enough that the timer has a chance to trigger it's event.
EDIT:
Along the lines of:
google.maps.event.addListener(map, 'bounds_changed', userInput);
function userInput() {
resetTimer();
}
Where resetTimer() clears/starts your timer. That would look something like:
var inputTimer = null;
var timeLimit = 500;
function resetTimer() {
if(inputTimer != null) clearInterval(inputTimer);
inputTimer = setTimeout('contactServer()', timeLimit);
}
function contactServer() {
// here goes an ajax call
}
I haven't tested that this compiles but it should give you the basic idea. This code has the advantage of being modular enough to work in many other languages with only minor edits. I follow a similar logic in ActionScript. Also, it's extremely easy to read, follow logically and maintain (6 months later when you forget how your code works).
I hope that helps in some way,
--gMale
精彩评论