I have a simple question that has me stumped:
In a Leaflet application, I have an event listener for clicking elements on the map:
marker.on('click', function () {
doStuff();
$('element').doStuff();
setView(this.getLatLng());
});
However, the setView method also triggers a 'map moved' event, which I do not want to fire. Using either plain JavaScript or jQuery, can I prevent any other event from firing while inside the click event function?
Edit: now with a Fiddle! To use it, just click anywhere on the map. As you can see, e.stopPropagation() does not work when placed inside the click event listener.
http://jsfiddle.net/gc6e4jbg/
I don't believe you can prevent moveend being fired. (NB: these aren't jQuery events - Leaflet has its own internal event system.) This is the source for setView:
setView: function (center, zoom) {
zoom = zoom === undefined ? this.getZoom() : zoom;
this._resetView(L.latLng(center), this._limitZoom(zoom));
return this;
}
_resetView always fires moveend at the end:
_resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
var zoomChanged = (this._zoom !== zoom);
if (!afterZoomAnim) {
this.fire('movestart');
if (zoomChanged) {
this.fire('zoomstart');
}
}
...
this.fire('moveend', {hard: !preserveMapOffset});
}
You could investigate customizing these functions to allow for suppression of the event.
Update:
Alternatively, you could change your moveend event handler. Have it track a flag, which you set when you don't want the normal operations to happen.
For example, you'll have set up your handler similar to:
map.on('moveend', myHandler);
Have myHandler do something like:
function myHandler(e) {
if (stopFlag) {
return;
}
else {
// Normal operation
...
}
}
Then just enable and disable stopFlag to control the flow. The advantage of this is that you don't have to publish a custom version of Leaflet with your application.
To be straight to the point: that will never work. By using stopPropagation/preventDefault you stop the click event from bubbling up through the dom. Nothing else. Once you execute L.Map's setView method it will always fire the moveend event, it's got nothing to do with the click event. It will also the fire movestart and move events and even the resetview event if you also set the zoomlevel in setView. That's just the way Leaflet works. You could always extend L.Map to write you own logic but i'm guessing you're better of finding another solution to your problem.
Use event.stopPropagation(), see the docs
marker.on('click', function (e) {
e.stopPropagation();
}
Here is my a bit more hackish solution.
At the beginning
Set moveend to true as a default value.
var moveend = true;
Inside a click event
Set moveend to false.
moveend = false;
map.setView(new L.LatLng(lat, lng));
moveend event
Do something if moveend is true. No matter what, set moveend to true for next run.
map.on('moveend', (e) => {
if (moveend) {
// Do something if moveend is enabled
}
moveend = true;
});
Related
When I use
$(".page").mousemove(function(event){});
As soon as the mouseup event comes, I no longer need this listener. Since I'll be applying the listener repetitively for different actions, it seems to me that these listeners might stick around and build up (needlessly wasting CPU) as the user activates the function many times. I'm not really sure how this works internally, that's just my guess.
Should I / how do I clear a mousemove JQuery event listener?
Here is the code:
$('.page').off('mousemove');
But please note that the following approach turns off all functions firing on mousemove. If you want to turn off a prticular function then you should do the following:
// Define function that fires on mousemove
function anyFunctionName () {
// Some code
}
// Set listener
$('.page').on('mousemove', anyFunctionName);
// Turn off only function called funcionName
$('.page').off('mousemove', anyFunctionName);
Another way for turning off a particular function would is defining a name for the event:
// Define function that fires on mousemove
function anyFunctionName () {
// Some code
}
// Set listener
$('.page').on('mousemove.anyEventName', anyFunctionName);
// Turn off only function fired on event called anyEventName
$('.page').off('mousemove.anyEventName');
I was able to get a working example using the more general .on and .off JQuery functions instead of their explicit handlers. Here is the code I used:
$('.page').on('mousedown', function() {
$(this).on('mousemove', function(event) {
// Do something meaningful
});
});
$('.page').on('mouseup', function() {
$(this).off('mousemove');
});
Here is a JFiddle Demo of it.
I have an input element with 2 events attached: focus and click. They both fire off the same helper function.
When I tab to the input, the focus event fires and my helper is run once. No problems there.
When the element already has focus, and I click on it again, the click event fires and my helper runs once. No problems there either.
But when the element does not have focus, and I click on it, BOTH events fire, and my helper is run TWICE. How can I keep this helper only running once?
I saw a couple similar questions on here, but didn't really follow their answers. I also discovered the .live jQuery handler, which seems like it could work if I had it watch a status class. But seems like there should be a simpler way. The .one handler would work, except I need this to work more than once.
Thanks for any help!
The best answer here would be to come up with a design that isn't trying to trigger the same action on two different events that can both occur on the same user action, but since you haven't really explained the overall problem you're coding, we can't really help you with that approach.
One approach is to keep a single event from triggering the same thing twice is to "debounce" the function call and only call the function from a given element if it hasn't been called very recently (e.g. probably from the same user event). You can do this by recording the time of the last firing for this element and only call the function if the time has been longer than some value.
Here's one way you could do that:
function debounceMyFunction() {
var now = new Date().getTime();
var prevTime = $(this).data("prevActionTime");
$(this).data("prevActionTime", now);
// only call my function if we haven't just called it (within the last second)
if (!prevTime || now - prevTime > 1000) {
callMyFunction();
}
}
$(elem).focus(debounceMyFunction).click(debounceMyFunction);
This worked for me:
http://jsfiddle.net/cjmemay/zN8Ns/1/
$('.button').on('mousedown', function(){
$(this).data("mouseDown", true);
});
$('.button').on('mouseup', function(){
$(this).removeData("mouseDown");
});
$('.button').on('focus', function(){
if (!$(this).data("mouseDown"))
$(this).trigger('click.click');
});
$(".button").on('click.click',evHandler);
Which I stole directly from this:
https://stackoverflow.com/a/9440580/264498
You could use a timeout which get's cleared and set. This would introduce a slight delay but ensures only the last event is triggered.
$(function() {
$('#field').on('click focus', function() {
debounce(function() {
// Your code goes here.
console.log('event');
});
});
});
var debounceTimeout;
function debounce(callback) {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(callback, 500);
}
Here's the fiddle http://jsfiddle.net/APEdu/
UPDATE
To address a comment elsewhere about use of a global, you could make the doubleBounceTimeout a collection of timeouts with a key passed in the event handler. Or you could pass the same timeout to any methods handling the same event. This way you could use the same method to handle this for any number of inputs.
Live demo (click).
I'm just simply setting a flag to gate off the click when the element is clicked the first time (focus given). Then, if the element gets focus from tabbing, the flag is also removed so that the first click will work.
var $foo = $('#foo');
var flag = 0;
$foo.click(function() {
if (flag) {
flag = 0;
return false;
}
console.log('clicked');
});
$foo.focus(function() {
flag = 1;
console.log('focused');
});
$(document).keyup(function(e) {
if (e.which === 9) {
var $focused = $('input:focus');
if ($focused.is($foo)) {
flag = 0;
}
}
});
It seems to me that you don't actually need the click handler. It sounds like this event is attached to an element which when clicked gains focus and fires the focus handler. So clicking it is always going to fire your focus handler, so you only need the focus handler.
If this is not the case then unfortunately no, there is no easy way to achieve what you are asking. Adding/removing a class on focus and only firing the click when the class isn't present is about the only way I can think of.
I have it - 2 options
1 - bind the click handler to the element in the focus callback
2 - bind the focus and the click handler to a different class, and use the focus callback to add the click class and use blur to remove the click class
Thanks for the great discussion everybody. Seems like the debouncing solution from #jfriend00, and the mousedown solution from Chris Meyers, are both decent ways to handle it.
I thought some more, and also came up with this solution:
// add focus event
$myInput.focus(function() {
myHelper();
// while focus is active, add click event
setTimeout(function() {
$myInput.click(function() {
myHelper();
});
}, 500); // slight delay seems to be required
});
// when we lose focus, unbind click event
$myInput.blur(function() {
$myInput.off('click');
});
But seems like those others are slightly more elegant. I especially like Chris' because it doesn't involve dealing with the timing.
Thanks again!!
Improving on #Christopher Meyers solution.
Some intro: Before the click event fires, 2 events are preceding it, mousedown & mouseup, if the mousedown is fired, we know that probably the mouseup will fire.
Therefore we probably wouldn't like that the focus event handler would execute its action. One scenario in which the mouseup wouldn't fire is if the user starts clicking the button then drags the cursor away, for that we use the blur event.
let mousedown = false;
const onMousedown = () => {
mousedown = true;
};
const onMouseup = () => {
mousedown = false;
// perform action
};
const onFocus = () => {
if (mousedown) return;
// perform action
};
const onBlur = () => {
mousedown = false;
// perform action if wanted
};
The following events would be attached:
const events = [
{ type: 'focus', handler: onFocus },
{ type: 'blur', handler: onBlur },
{ type: 'mousedown', handler: onMousedown },
{ type: 'mouseup', handler: onMouseup }
];
In one of our projects we're using Leaflet along with Leaflet.markercluster plugin. Looking through the Leaflet's sources I found that it appends _collapse() function to the map's click event, so whenever I click on the map it contracts previously expanded cluster.
Now, I want to disable this behavior. If the cluster is expanded, then I just want to deselect all of its markers on click event (and don't contract the cluster itself). Here is the piece of my code:
map.on('click', function(e) {
scope.deselectAllMarkers();
});
I tried to add the following lines in the end of this one-line callback in order to stop propagation of click event:
scope.L.DomEvent.stopPropagation(e);
scope.L.DomEvent.preventDefault(e);
scope.L.DomEvent.stop(e);
scope.L.DomEvent.stopPropagation(e.originalEvent);
scope.L.DomEvent.preventDefault(e.originalEvent);
scope.L.DomEvent.stop(e.originalEvent);
And none of them works. Default listener which is hidden inside of the Leaflet sources keeps its invocation whenever I click on the map. Am I missing something?
I know that this answer is quite late, but if someone is interested in a solution, here is how i have solved it.
This snippet here below is an example of binding a function to the click event.
map.on('click', doSomething);
Actually, after checking leaflet's API and some geekish debugging, it seems that the event returns an object, not the event itself. The event itself is wrapped into a field within the returned object.
var doSomething = function(map) {
// stop propagation
map.originalEvent.preventDefault();
};
When using the above snippet, the event bubbling has stopped, something which i wanted, and probably what you wanted.
This one worked for me...
var div = L.DomUtil.get('div_id');
if (!L.Browser.touch) {
L.DomEvent.disableClickPropagation(div);
L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
} else {
L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
}
Thanks to https://gis.stackexchange.com/questions/104507/disable-panning-dragging-on-leaflet-map-for-div-within-map
I know that this answer is event more quite late, but as in jquery you can use .off
map.on('click', doSomething);
map.off('click');
It works fine for any leaflet events.
I use it for 'zoomend' event to be triggered one time only.
map.on('moveend', function(e){
console.log("any code");
map.off('moveend');
});
You can't override the event propagation from an event handler. You need to use the builtin Leaflet helper after the page has loaded, like this:
$('.element').each (i,el)->
L.DomEvent.disableClickPropagation(el);
In the end I've solved the issue by manual removal of default click handler which was invoking the _collapse() method, as far as I remember. Dirty, but it did the trick.
You have use like this event.stopPropagation()
map.on('click', function(e) { //don't forget to pass this 'e' event parameter
e.preventDefault();
scope.deselectAllMarkers();
e.stopPropagation();
return false;
});
Try anyone of this
1.event.stopPropagation()
2.event.preventDefault()
3.return false
I have a problem using balloons in google earth.
I have some markers on the map, upon clicking on a marker, a balloon popup is shown containing some data, now when I click on the close button of that balloon, the click event of the map is also triggered which is really annoying as I have a handler attached with the map click event.
I tried everything including using event.stopPropagation() in the 'beforeclose' event of the htmlDivBalloon but still nothing works.
Anyone has an idea about that ?
Best Regards
John Tadros
The chances are you are not handling the default event or you are not screening which objects the event acts in the handler "attached with the map click event". You haven't shown any code, so it is hard to say exactly how to fix it - but a generic way to handle this is as follows.
// listen for mousedown on the window
google.earth.addEventListener(ge.getWindow(), 'mousedown', function(e) {
var type = e.getTarget().getType();
if (type == 'KmlPlacemark') {
// prevent the default event for placemarks, stop Propagation
e.preventDefault();
e.stopPropagation();
} else if(type == 'GEGlobe') {
// do something with the globe...
}
// etc...
});
I am using google maps + javascript + php in my application.
I want to know two things:
In google maps,
does moveend event ALWAYS gets fired
AFTER zoomend/dragend (whichever of
two) event occurs.
When I click zoom icon on google map
or scroll the mouse wheel to zoom,
the zoomend event gets fired more
than once. If I zoom in one step
using + icon on map, the zoomend
event gets fired twice or sometimes
more. any possible loophole.
And so want to know how to stop further event propogation in javascript. (remember I need not use clearListeners as it will forever ignore event handler which is undesirable).
Thank you.
I set up listeners for 'moveend', 'zoomend', and 'dragend' to try it out.
GEvent.addListener(map, "moveend", function() { console.log('moveend'); });
GEvent.addListener(map, "zoomend", function() { console.log('zoomend'); });
GEvent.addListener(map, "dragend", function() { console.log('dragend'); });
It appears that 'moveend' always fires after 'zoomend' or 'dragend'.
However, no events ever fired more than once at a time. Maybe you accidentally set up two simultaneous listeners. You shouldn't need to use stopPropagation or cancelBubble.
you could try just reuturning false or null from the event.
If that doesn't work trying using "event.cancelBubble = true" or "event.stopPropagation"