This question is asked often, but never really answered well. Let's see if we can remedy it!
Event Propagation
Google allows you to bind to events in a Google Map View via their API using event handlers.
Sometimes you may bind your event handler to an event that Google itself is already bound to. Thus, when your event fires and does whatever you told it to do you may find Google also doing its own little thing at the same time.
Hmm, can I handle the event so my code runs, but stop the event from continuing on and firing Google's event handler?
You sure can! Welcome to Event Propagation (aka Event Bubbling).
Take a look at this code
Here I bind an event handler to double clicking on the Google Map:
var aListener = google.maps.event.addListener(map, 'dblclick', function(event) {
// Try to prevent event propagation to the map
event.stop();
event.cancelBubble = true;
if (event.stopPropagation) {
event.stopPropagation();
}
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
});
Here map is a Google Map object to bind to.
This doesn't work. The event bubbles, and the map zooms in. I don't know why.
You ask, have you read the documentation?
Indeed. The documentation says to use event.stop();
I have looked at what others are saying. This issue is exactly my problem. It was marked as fixed, but the solution does not work.
Ideas?
Workaround
A possible workaround for the doubleclick event is to disable Google's default behavior when you need it to not fire, and then re-enable it later.
You do this with the disableDoubleClickZoom argument. See the documentation here.
Here is some code to disable:
map.set("disableDoubleClickZoom", true);
Now to re-Enable:
map.set("disableDoubleClickZoom", false);
Of course, you can set the property in the MapOptions argument for when the map object is created in the first place.
UPDATE: Unfortunately, I had to find out that Firefox does not make the current event accessible via window.event thus this code won't work there. I haven't found a workaround for that, yet.
It turns out the fix to your code is minimal: just remove the event parameter from your event handler function, thus accessing the global window.event object inside the handler.
The following example code worked for me in IE and Chrome, but not Firefox:
google.maps.event.addListener(map, "dblclick", function(googleMapsEvent) {
console.debug("caught double click");
// reference the global event object
// ignore the googleMapsEvent passed in by Google Maps!
event.preventDefault();
});
This answer put me on the right track!
Not a solution but some ideas, as requested ...
event.stopPropagation() is not a generalised panacea for issues of this type for two reasons :
your event handler may be responding to an already bubbled event, ie it too late to prevent event handling (zoom in this case) which was triggered earlier in the bubbling cycle.
it may not be a bubbling issue but an event being handled by at the same level. .addListener() implements an observer pattern, meaning that new additions are added to the element's "concrete observer" queue without removing any handlers previously added by the same mechanism.
A workaround is almost undoubtedly available for both of these situations but would probably require "inside knowledge" of Google Maps to solve.
Maybe someone else knows more than I.
Related
My situation is that I am trying to trigger a single event using the jQuery .trigger() method. However the element I am triggering has multiple click event listeners.
Actually finding what these listeners are and what they trigger from the source code is probably not viable as its included in the sites main JS file and its all minified and pretty much unreadable.
At the moment I know that the element when clicked performs some kind of ajax call and loads more data into the DOM of the page (which is what i want to trigger), however it also displays an overlay (which is what I want to suppress temporarily).
As its just an overlay there are workaround I can make; using a display:none on it straight after click etc. However it would be much more elegant if i could somehow suppress all click events on this element except the desired event.
Any ideas if this is actually possible? And if so how I would go about it?
You need to register your own event at the top of the event chain. And cancel the event chain in your event. Here is a solution with writing a custom jquery extention.
$.fn.bindFirst = function (which, handler) {
var $elm = $(this);
$elm.unbind(which, handler);
$elm.bind(which, handler);
var events = $._data($elm[0]).events;
var registered = events[which];
registered.unshift(registered.pop());
events[which] = registered;
}
$("#elm").bindFirst("click", function(e) {
// edit: seems like preventing event does not work
// But your event triggers first anyway.
e.preventDefault();
e.stopPropagation();
});
Reference:
https://gist.github.com/infostreams/6540654
EDIT:
https://jsfiddle.net/8nb9obc0/2/
I made a jsFiddle and it seems like event preventing does not work in this example. There might be another solution.
I have the below JQuery eventhandler. I want to stop all navigations on a web page.
$(document).click(function(event) {
event.stopPropagation();
event.preventDefault();
event.cancelBubble = true;
event.stopImmediatePropagation();
$(document).css('border-color','');
$(document).css('background-color','');
$(event.target).css('border-color','yellow');
$(event.target).css('background-color','#6BFF70');
return false;
});
When I use this on Facebook Login page, it stops all navigations. But in Google home page, "I'm Feeling Lucky" button still navigates to next page. How do I avoid it?
I'm using JavaFX browser by the way. It is similar to Safari browser.
If I load the Google search page, and execute this at the console:
document.body.addEventListener(
"click",
function (ev) { ev.stopPropagation(); ev.preventDefault(); },
true);
then I cannot click the "I'm Feeling Lucky" button anymore. The key is to use the third parameter and set it to true. Here is what MDN [says] about it:
useCapture Optional
If true, useCapture indicates that the user wishes to initiate capture. After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
(Emphasis added.)
What you tried to do does not work because your event handler is on document, and thus will be called after any event handlers on the children of the document. So your handler cannot prevent anything.
With useCapture set to true, you can operate on the event before it gets a chance to be passed to the child element. I do not know of a way to have jQuery's event handlers work in the way you get with useCapture. Barmar's answer here says you can't use jQuery to set such handler. I'm inclined to believe him.
99.99% of webpages won't be able to have their navigation stopped by stopping event propagation for the reason I commented (you can't stop the event before it triggers all handlers for the initial target of the event). If preventing navigation is all you are interested in, I recommend using the window.onbeforeunload event, which is made for this exact situation.
Here is an example: http://jsfiddle.net/ejreseuu/
HTML:
google
JS:
window.onbeforeunload = function() {
return "Are you sure?"
}
There is no way to not have a confirmation box that I know of, as code that locks the user out of navigating away no matter what they do is generally malicious.
preventDefault() should not work in this case, cause Google relied on custom event listeners to handle click events on this button. While preventDefault()
prevents browser's default behavior.
For example, if this button was of type="submit", preventing default on click event would prevent browser's default behavior, which is submitting a form. But in this case click is handled by eventListeners added to the button itself. preventDefault() won't affect catching an event by them. Nor stopPropagation(), because it stops propagation of event to higher levels of DOM, while other eventListeners on the same level (button in our case) still get the event. stopImmediatePropagation() could work in theory, but only if your eventListener was added before google's.
So the easiest way to stop propagation is to stop an event before it reaches button node, and that's on capture phase, because button is the lowest element in the hierarchy. This can be done by passing true argument while adding eventListener
document.body.addEventListener("click", function (event) {
event.stopPropagation();
}, true);
This way event will be stopped before bubble phase, and so before it reaches eventListeners added to the button. More on capture and bubble phases here
Note that preventDefault() is not needed in this case. Actually, this button's event listeners are to prevent default themselves. Here are those eventListeners, for click and keyup respectively:
d = function(a) {
c.Xa.search(c.yc(), b);
return s_1vb(a)
}
function(a) {
13 != a.keyCode && 32 != a.keyCode || d(a)
}
note call to s_1vb, here is its sourse:
s_1vb.toString();
/*"function (a){
a&&(a.preventDefault&&a.preventDefault(),a.returnValue=!1);
return!1
}"*/
Basically its a function that take an event and do everything possible to prevent browser's default behavior
By the way, default behavior can be canceled on any stage of event flow (se Events Specification), including the very last stage, when it reached document. Only after it passed "through" all eventListeners uncanceled, browser should execute its default behavior. So attaching your listener to document was not the reason preventDefault() didn't work, it was because it was the wrong guy for the job :)
Try this:
$('body').click(function(event) {
event.stopPropagation();
event.preventDefault();
event.cancelBubble = true;
event.stopImmediatePropagation();
$(document).css('border-color','');
$(document).css('background-color','');
$(event.target).css('border-color','yellow');
$(event.target).css('background-color','#6BFF70');
return false;
});
Try to bind not only to click event, but as well on mousedown event.
Try this css:
body * {
pointer-events: none;
}
or in jQuery:
$("body *").css("pointer-events", "none");
Try declaring a new window event and then stopping the propagation from there:
var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation)
{
e.stopPropagation();
}
Note that Google uses jsaction="..." instead of onclick="...". Try to use it's unbind method on the specified button.
Also you can use dynamic attachment, like:
$(document).on('click', '*', function
Or throw new Error()(just as a dirty hack)
I need to do some operations when the map is paned or zoomed, so I attached a callback to the event moveend.
map.on('moveend', function() {
// code stuff
});
It works fine, but when the page is load the event is fired three times and I don't know why.
Probably because during its creation the map is moved.
To avoid this i tried to wait the load event before subscribing moveend event, but nothing changed. So I tried to attach it within whenReady callaback, but again it is fired three times.
map.whenReady(function() {
map.on('moveend', function() {
// code stuff
});
});
Finally, I discovered that after the resize event it works quite fine: moveend is fired jonly one time. But I really believe there is a best way to fix the problem.
Another solution could be to attach my callback to both events zoomend and dragend, to cover paning and zooming cases.
But I didn't find a way to do it.
Thank you for your help.
The best solution I found is to attach the callback to both events:
map.on('zoomend', function() {
// callback
});
map.on('dragend', function() {
// callback
});
Although this way the code is a bit replicated, this is by far the best solution.
For others looking into this, research the options.debounceMoveend option on the invalidateSize function. It's mentioned in briefly in the documentation, but unfortunately it looks like it's only for that function, rather than generally for the moveend event.
[...] If options.debounceMoveend is true, it will delay moveend event so that it doesn't happen often even if the method is called many times in a row.
Reference to the line in source code (L3541)
You can use mouseenter and mouseleave events.
Example:
block.addEventListener('mouseenter', ()=>{
//some code when hover
})
block.addEventListener('mouseleave', ()=>{
// some code when leaving block
})
link to developer.mozilla.org
The 'zoomend' and 'dragend' option didn't work for me. I searched a lot for a suitable option and realized that the "moveend" event fires several times because this event is created every time you move the map. Therefore it is necessary to stop this event. I got out of the situation in this way.
Immediately after the map was initialized, I wrote:
map.off('moveend');
and for me it worked. Now it works fine.
I will be very happy if this is useful to someone.
I have a site that uses AJAX to navigate. I have two pages that I use a click and drag feature using:
$(".myDragArea").mousedown(function(){
do stuff...
mouseDrag = true; // mouseDrag is global.
});
$("body").mousemove(function(){
if (mouseDrag) {
do stuff...
}
});
$("body").mouseup(function(){
if (mouseDrag) {
do stuff...
mouseDrag = false;
}
});
I just type that out, so excuse any incidental syntax errors. Two parts of the site use almost identical code, with the only difference being what is inside the $("body").mouseup() function. However, if I access the first part, then navigate to the second part, the code that runs on mouseup doesn't change. I have stepped through the code with Firebug, and no errors or thrown when $("body").mouseup() is run when the second part loads.
So, why doesn't the event handler change when I run $("body").mouseup() the second time?
Using $("body").mouseup( ... ) will add an event handler for the body that is triggered at mouseup.
If you want to add another event handler that would conflict with current event handler(s) then you must first remove the current conflicting event handler(s).
You have 4 options to do this with .unbind(). I'll list them from the least precise to the most precise options:
Nuclear option - Remove all event handlers from the body
$("body").unbind();
This is pretty crude. Let's try to improve.
The elephant gun - Remove all mouseup event handlers from the body
$("body").unbind('mouseup');
This is a little better, but we can still be more precise.
The surgeon's scalpel - Remove one specific event handler from the body
$("body").unbind('mouseup', myMouseUpV1);
Of course for this version you must set a variable to your event handler. In your case this would look something like:
myMouseUpV1 = function(){
if (mouseDrag) {
do stuff...
mouseDrag = false;
}
}
$("body").mouseup(myMouseUpV1);
$("body").unbind('mouseup', myMouseUpV1);
$("body").mouseup(myMouseUpV2); // where you've defined V2 somewhere
Scalpel with anesthesia (ok, the analogy's wearing thin) - You can create namespaces for the event handlers you bind and unbind. You can use this technique to bind and unbind either anonymous functions or references to functions. For namespaces, you have to use the .bind() method directly instead of one of the shortcuts ( like .mouseover() ).
To create a namespace:
$("body").bind('mouseup.mySpace', function() { ... });
or
$("body").bind('mouseup.mySpace', myHandler);
Then to unbind either of the previous examples, you would use:
$("body").unbind('mouseup.mySpace');
You can unbind multiple namespaced handlers at once by chaining them:
$("body").unbind('mouseup.mySpace1.mySpace2.yourSpace');
Finally, you can unbind all event handlers in a namespace irrespective of the event type!
$("body").unbind('.mySpace')
You cannot do this with a simple reference to a handler. $("body").unbind(myHandler) will not work, since with a simple reference to a handler you must specify the event type ( $("body").unbind('mouseup', myHandler) )!
PS: You can also unbind an event from within itself using .unbind(event). This could be useful if you want to trigger an event handler only a limited number of times.
var timesClicked = 0;
$('input').bind('click', function(event) {
alert('Moar Cheezburgerz!');
timesClicked++;
if (timesClicked >= 2) {
$('input').unbind(event);
$('input').val("NO MOAR!");
}
});
Calling $("body").mouseup(function) will add an event handler.
You need to remove the existing handler by writing $("body").unbind('mouseup');.
jQUery doesn't "replace" event handlers when you wire up handlers.
If you're using Ajax to navigate, and not refreshing the overall DOM (i.e. not creating an entirely new body element on each request), then executing a new line like:
$("body").mouseup(function(){
is just going to add an additional handler. Your first handler will still exist.
You'll need to specifically remove any handlers by calling
$("body").unbind("mouseUp");
I am looking for the most proper and efficient way to bind Javascript events; particularly the onload event (I would like the event to occur after both the page AND all elements such as images are loaded). I know there are simple ways to do this in jQuery but I would like the more efficient raw Javascript method.
There are two different ways to do it. Only one will work; which one depends on the browser. Here's a utility method that uses both:
function bindEvent(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
element.attachEvent('on'+type, handler);
}
}
In your case:
bindEvent(window, 'load', function() {
// all elements such as images are loaded here
});
I know you did only ask about how to bind events. But Ooooo boy the fun doesn't end there. There's a lot more to getting this right cross-browser than just the initial binding.
#d.'s answer will suffice just fine for the specific case of the load event of window you're looking for. But it may give novice readers of your code a false sense of "getting it right". Just because you bound the event doesn't mean you took care to normalize it. You may be better of just fixing window.onload:
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
// now do your thing here
}
}(window.onload))
But for the general case of event binding #d.'s answer is so far from satisfying as to be frustrating. About the only thing it does right is use feature-detection as opposed to browser detection.
Not to go on too much of a rant here but JavaScript event binding is probably the #1 reason to go with a JavaScript library. I don't care which one it is, other people have fixed this problem over and over and over again. Here're the issues in a home-brewed implementation once inside your handler function:
How do I get a hold of the event object itself?
How do I prevent the default action of the event (eg clicking on a link but not navigating)
Why does this point to the window object all the time?
(Re mouse events) What are the x/y coords of the event?
Which mouse button triggered the event?
Was it a Ctrl-click or just a regular click?
What element was the event actually triggered on?
What element is the event going to? (ie relatedTarget, say for blur)
How do I cancel the bubbling of the event up through its parent DOM?
(Re event bubbling) what element is actually receiving the event now? (ie currentTarget)
Why can't I get the freaking char code from this keydown event?
Why is my page leaking memory when I add all these event handlers?? Argh!
Why can't I Unbind this anonymous function I bound earlier?
And the list goes on...
The only good reason to roll your own in these days is for learning. And that's fine. Still, read PPK's Intro to browser events. Look at the jQuery source. Look at the Prototype source. You won't regret it.
Something like that
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
$(".loading-overlay .spinner").fadeOut(300),
$(".loading-overlay").fadeOut(300);
$("body").css({
overflow: "auto",
height: "auto",
position: "relative"
})
}
}(window.onload));
window.onload = function() {
// ...
};