views:

56

answers:

3

TLDR I have a function that runs on the end of a pan in an openlayers map. Don't want it to fire continously.


I have a function that runs on the end of panning a map. I want it so that it will not fire the function until say 3 secconds after the pan has finished. Although I don't want to queue up the function to fire 10 or so times like setTimeout is currently doing.

How can I delay a function from running for n seconds then run it only once no matter how many times it has been called?

    map.events.register("moveend", map, function() {
      setTimeout(SetLocation, 5000);
    });

Moveend:

moveend - triggered after a drag, pan, or zoom completes

The code above even using setTimeout(func, delay); still fires multiple times when it runs. How can I prevent this?


+1  A: 

This is precisely what setTimeout is for. If setTimeout is calling the function 10 times, there's something wrong with your code, which you didn't post.

Also keep in mind that setTimeout will not halt the script.

Daniel
+2  A: 

Well, meeting your requirements, you could build a simple function wrapper:

var executeOnce = (function (fn, delay) {
  var executed = false;
  return function (/* args */) {
    var args = arguments;
    if (!executed) {
      setTimeout(function () {
        fn.apply(null, args); // preserve arguments
      }, delay);
      executed = true;
    }
  };
});

Usage examples:

With your code:

map.events.register("moveend", map, executeOnce(SetLocation, 5000));

Other usages:

var wrappedFn = executeOnce(function (a, b) {
  alert(a + ' ' + b);
}, 3000);

wrappedFn('hello', 'world');
wrappedFn('foo', 'bar'); // this won't be executed...

The wrapped function will be delayed the specified amount of time and executed only once.

CMS
Currently is not firing the function. Trying exactly what you posted above. thanks for the fast reply :) /Edit: it is firing it but when it does fire it is firing it about 50-60 times at once?
Thqr
It is still causing the function "SetLocation" to fire multiple times using the above example exactly.
Thqr
Ozaki, that's strange... are you sure you are registering the `moveend` event only once?
CMS
See above for (moveend) but yes. And even if I did a zoom / pan / drag at the same time it should only fire a maximum of 3 and its going at least 10 or so. Even if it were to fire 3 it should halt the setTimeout from the start anyways right?
Thqr
@CMS: why did you wrap the first function with a ( )? I've seen that before to run in the global scope, but in your case...?
ItzWarty
@ItzWarty, at first when I started to write the function, I was thinking to invoke it immediately, e.g. `var foo = (function () {...})();`, for that cases the [parens add readability](http://michaux.ca/articles/an-important-pair-of-parens), but in this case I don't see any benefit...
CMS
@Ozaki, could you build a working example that exposes the undesired behavior, and put it somewhere, like in http://jsbin.com in order to help you better ?
CMS
What's happening is probably that `map.events.register("moveend", map, executeOnce(SetLocation, 5000));` is being called multiple times.
Gabe
I would say that if it is being called multiple times is there a way to prevent it and have it only fire once.@CMS The file I am working from is a tad large. All I can attach is this function as it is the only reference to the function and the moveend is an OpenLayers reference.
Thqr
@Ozaki - Just checked out the OpenLayers API. `moveend` should only trigger once ideally. It might be better to step through the code in a debugger to find out why it is being called multiple times rather than patching it up to execute only once as it is very likely to cause more problems later on. See this small setup on jsfiddle for `moveend` - http://jsfiddle.net/MFMaz/.
Anurag
I am only recieving the call once. Debugging in firebug MAYBE twice if i zoom and pan a the same time. :S But when I run it not debugging it fires multiple times :S this is making no sense right about now.
Thqr
That does seem weird. I am guessing the event handlers are getting registered several times, something similar to http://jsfiddle.net/MFMaz/1/ - where a new handler gets attached every 3 seconds. The `moveend` handler should be registered only once.
Anurag
Yes looking at it now. OL seems to take priority over loading itself compared to the timeout etc. So the delay gets increased it finishes loading and it is firing them all at once. I am unsure but i beleive this is what it is doing by the looks of things
Thqr
Got it going now had alot of colllisions with other things being blocked by the OL / JSON calls. Comes with a bit of everything so thanks everyone for the help ^^
Thqr
@Ozaki - Glad you got it working. Cheers!
Anurag
+1  A: 

For UI delays I would recommend using 'clearTimeout' in conjunction with 'setTimeout'. A call to 'setTimeout' returns an ID that is usually ignored. If, however, you store the ID, next time you are about to call 'setTimeout' you can cancel the previous 'setTimeout' (as though you never called it).

What I assume is happening in your case is:

(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
setTimeout (2nd)
...
callback from 1st setTimeout is called
callback from 2nd setTimeout is called
...

If, however, you use clearTimeout, you'll have:

(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
clearTimeout (1st)
setTimeout (2nd)
...
callback from last setTimeout is called

To update the JavaScript code you provided:

var delayedSetLocationId = -1;
...
map.events.register("moveend", map, function() {
    if (delayedSetLocationId >= 0) {
        clearTimeout(delayedSetLocationId);
    }
    delayedSetLocationId = setTimeout(SetLocation, 5000);
}); 
...
function SetLocation(...) {
    delayedSetLocationId = -1; // setTimeout fired, we can't cancel it now :)
    ...
}
Milan Gardian
Using the code you provided above. It has a delay of a good 10-20secs.Then fires off a whole bunch. The only thing i can think of now is that it queues it waits for the OL map to finish loading everything it has the fires off the queue.
Thqr
I'm not sure I follow -- if you setTimeout to 5000 milliseconds, how can you have a delay of 10-20 seconds? The SetLocation function should be called exactly 5 seconds after the last time the "moveend" event was intercepted. I would recommend: a) add a trace statement to the "moveend" event handler (viewable from IE developer tools/Firebug/Chrome console etc) and investigate how often is this event fired; b) set a breakpoint in the SetLocation function and look at the call stack to investigate who calls it
Milan Gardian
One more thing - you may want to try cancelling both bubbling and processing of the "moveend" event so that your handler indicates you took notice and consumed the event.
Milan Gardian