开发者

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜