Before I start writing huge swathes of code that don't work I thought I'd ask this question.
event.preventDefault() only cancels the default action of the click event doesn't it?
Theoretically I should be able to bind mutiple click event handlers in jQuery to a given target to perform different actions like Ajax posts and Google tracking.
Am I wrong?
event.preventDefault() only cancels the default action of the click event doesn't it?
It cancels the browser's default action of the event (not just the click event) (W3C docs, jQuery docs). So for instance, in the form submit event, it prevents the form being submitted by the browser. It doesn't stop anything you're doing in code, and it doesn't stop bubbling; that's what stopPropagation is for (W3C docs, jQuery docs).
So say you have a link in a div, and you have the click event hooked on both the link and the div. If the link's event handler calls preventDefault, the browser won't do its default action (following the link), but the event continues to bubble up the DOM to the link's parent element, the div, and so you'll see the event on your click handler there, too. Any actions you're taking in code in either handler will be unaffected by your calling preventDefault.
In your comment below, you ask about multiple handlers on the same element. Neither preventDefault nor stopPropagation affects those, they'll still get fired...unless you use stopImmediatePropagation, which tells jQuery to stop the event dead in its tracks (but doesn't prevent the browser's default action).
I should probably round this out by saying that if you return false from your event handler, that tells jQuery to prevent the default and stop bubbling. It's just like calling preventDefault and stopPropagation. It's a handy shortcut form for when your event handler is taking full control of the event.
So, given this HTML:
<div id='foo'><a href='http://stackoverflow.com'>Q&A</a></div>
Example 1:
// Here we're preventing the default but not stopping bubbling,
// and so the browser won't follow the link, but the div will
// see the event and the alert will fire.
$("#foo").click(function() {
alert("foo clicked");
});
$("#foo a").click(function(event) {
event.preventDefault();
});
Example 2:
// Here we're stopping propagation and not preventing the default;
// the browser will follow the link and the div will not be given
// a chance to process the event (no alert, and more to the point,
// code in the div's handler can't prevent the default)
$("#foo").click(function() {
alert("foo clicked");
});
$("#foo a").click(function(event) {
event.stopPropagation();
});
Example 3 (you'll only rarely see this):
// Here we're doing both, and so the browser doesn't follow the
// link and the div doesn't see the event (no alert).
$("#foo").click(function() {
alert("foo clicked");
});
$("#foo a").click(function(event) {
event.preventDefault();
event.stopPropagation();
});
Example 4:
// Shorter version of Example 3, exactly the same effect
$("#foo").click(function() {
alert("foo clicked");
});
$("#foo a").click(function() {
return false;
});
Related
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 am trying to use Javascript to emulate the CSS :target pseudo-class so as to capture all events that result in an element on page being targeted. I've identified 3 trigger events:
window.location.hash already targets an element of the same ID on initialisation
An anchor targeting the element is clicked
The hashchange event is fired independently of the above (for example via the window.history API)
Scenario 2 is important as a distinct case since I would want to invoke the click event's preventDefault. The simplified code for this scenario follows:
$('body').on('click', 'a[href*=#]', function filterTarget(clickEvent){
$(this.hash).trigger('target', [clickEvent]);
});
The problem comes when trying to implement scenario 3:
$(window).on('hashchange', function filterTarget(hashChangeEvent){
$(this.hash).trigger('target', [hashChangeEvent]);
});
If a target handler doesn't cancel the native behaviour for scenario 2, it will be triggered again when the native behaviour causes the resulting hashchange event. How can I filter out these edge cases?
POST-SOLUTION EDIT:
roasted's answer held the key — handle a namespaced hashchange event, then unbind and rebind the handler based on logic handled inside the click handler and its preventDefault. I wrote up the full plugin here.
If i understand it, you don't want the hashchange event to be fired if an anchor tag is clicked. You could then set your logic using namespaced events:
DEMO
$('body').on('click', 'a[href*=#]', function (clickEvent) {
filterTarget(clickEvent,this);
$(window).off('hashchange.filter').on('hashchange.tmp', function () {
$(this).off('hashchange.tmp').on('hashchange.filter', filterTarget);
});
});
$(window).on('hashchange.filter', filterTarget);
function filterTarget(event,elem) {
$(elem?elem.hash:window.location.hash).trigger('target', [event]);
//you could filter depending event.type
alert(event.type + '::'+ (elem?elem.hash:window.location.hash));
}
if the click is setting the hash with the fragment anyway, just throw away duplicates in the hash change event:
onhashchange=function(e){
if(e.newURL == e.oldURL ){return; }
//do your normal hashchange event stuff below:
};
ref: https://developer.mozilla.org/en-US/docs/Web/API/window.onhashchange
this fixes cascade issues no matter what invoked the change.
Seems like you could use mousedown instead of click, if you're going to be calling preventDefault on it. Then presumably the hashchange would not be triggered.
My goal is to prevent all the click events (hiding/showing of elements in the HTML when clicked) unless a certain condition is met (the user has a certain word in an input element).
So i tried to add that logic to the click handler of the document or "html" but the click handler of the other element fired first because of bubble up.
So i tried attaching that logic to "*", and now that click handler fires first- but propagates it to the the other element too, ignoring stopPropagation, preventDefault and return false.
$(document).ready(function(){
$("*").click(function(event){
if ($("#user").val() !== "admin"){
console.log("1");
event.stopPropagation();
event.preventDefault();
return false;
}
});
$("#user").click(function(event){
console.log("2");
// do something
});
});
Why "2" is written to the console after "1" when there shouldn't be any further propagation because of return false/stopPropagation?
How else can i achieve my goal using jQuery?
Thanks!
stopPropagation() prevents the event propagating any further up the ancestor tree. However, it doesn't prevent the remaining event handlers on the current from being fired.
To do this (prevent further propagation and prevent any further event handlers on the current element from being fired), you need to call stopImmediatePropagation() (instead, not as well).
Attaching an event handler to every element in this manner, and calling stopImmediatePropagation() (as well as preventDefault()) will prevent all clicks from having an effect; providing no event handlers are bound before (as handlers are executed in order; you can't undo a handler which has already fired).
This doesn't make it nice though, as finding, enumerating over, and attaching a handler to every element is pretty costly.
To make it nicer, your options are either:
Attach a click event to document, and simply preventDefault() and sacrifice stopImmediatePropagation().
Check the state of #user in each event handler; you can ease the pain of this by rolling your own wrapper function;
function checkUserState(then) {
return function () {
if ($("#user").val() !== "admin") {
then.apply(this, arguments);
}
};
};
... use like so;
$("#user").click(checkUserState(function(event){
console.log("2");
}));
As noted in the comments, I'm purposefully avoiding the suggestion of using event delegation, as whilst allows attaching only one event handler instead of n, it doesn't allow you to stopPropagation() of events.
When I use event.preventDefault() on a link it works, however when I use it on a button doesn't!
DEMO
My code:
<a id="link" href="http://www.google.com">link</a>
<button id="button" onclick="alert('an alert')">button</button>
$('#link').click(function(event){
event.preventDefault();
});
$('#button').click(function(event){
event.preventDefault();
});
Link action is cancelled, but when I click on the button, still executes the onClick action.
Any help? what I want to do is to prevent the button onClick action without changing the button html (I know how to do
$('#button').removeAttr('onclick');
You want event.stopImmediatePropagation(); if there are multiple event handlers on an element and you want to prevent the others to execute. preventDefault() just blocks the default action (such as submitting a form or navigating to another URL) while stopImmediatePropagation() prevents the event from bubbling up the DOM tree and prevents any other event handlers on the same element from being executed.
Here are some useful links explaining the various methods:
http://api.jquery.com/event.preventDefault/
http://api.jquery.com/event.stopPropagation/
http://api.jquery.com/event.stopImmediatePropagation/
However, since it still doesn't work it means that the onclick="" handler executes before the attached event handler. There's nothing you can do since when your code runs the onclick code has already been executed.
The easiest solution is completely removing that handler:
$('#button').removeAttr('onclick');
Even adding an event listener via plain javascript (addEventListener()) with useCapture=true doesn't help - apparently inline events trigger even before the event starts descending the DOM tree.
If you just do not want to remove the handler because you need it, simply convert it to a properly attached event:
var onclickFunc = new Function($('#button').attr('onclick'));
$('#button').click(function(event){
if(confirm('prevent onclick event?')) {
event.stopImmediatePropagation();
}
}).click(onclickFunc).removeAttr('onclick');
you need stopImmediatePropagation not preventDefault. preventDefault prevents default browser behavior, not method bubbling.
http://api.jquery.com/event.stopImmediatePropagation/
http://api.jquery.com/event.preventDefault/
The preventDefault function does not stop event handlers from being triggered, but rather stops the default action taking place. For links, it stops the navigation, for buttons, it stops the form from being submitted, etc.
What you are looking for is stopImmediatePropagation.
you can try this:
$('#button').show(function() {
var clickEvent = new Function($(this).attr('click')); // store it for future use
this.onclick = undefined;
});
DEMO
It have helped me
function goToAccessoriesPage(targert) {
targert.onclick.arguments[0].preventDefault();
...
}
Is there any way to prevent a click from an <a> triggering delegated click handlers on its parent, while allowing the the <a>'s default behavior to occur (navigating to the href).
Here's an example that illustrates what I'm asking.
<div class="top">
<div class="middle">
link
</div>
</div>
And my JavaScript:
$(".top").delegate(".middle", "click", function(event) {
alert("failure");
});
$(".top").delegate(".link", "click", function(event) {
// ???
});
In this case, I want to be navigated to google.com when I click the link, but must NOT see the alert("failure") on my way out.
There are a few restrictions to the solution:
All event handlers must be delegated off of $(".top"), as I potentially have thousands of these in the page.
The navigation must be accomplished using browser default behavior, rather than window.location = $(this).attr("href") or similar
Using normal event binding, I could do an e.stopPropagation() in a click handler for the <a>, but that won't work due to the nature of delegation. jQuery provides another method called .stopImmediatePropagation() that describes what I want (preventing other handlers on current element, in this case the element that holds the delegated handlers), but does not actually accomplish it in this case. That might be a bug in .delegate(), I'm not sure.
Returning false from the <a>'s click handler will prevent the other handler from running, but will also do a .preventDefault(), so the browser will not navigate. Basically, I'm wondering what return false; does that e.stopImmediatePropagation(); e.preventDefault(); does not. Based on the docs, they should be equivalent.
For a live demo of the above code, here's a JSFiddle: https://jsfiddle.net/CHn8x/
event.stopImmediatePropagation() is indeed what you're after, but remember that order matters here since .delegate() listens at the same level, so you need to reverse your bindings, like this:
$(".top").delegate(".link", "click", function(event) {
event.stopImmediatePropagation();
});
$(".top").delegate(".middle", "click", function(event) {
if(!event.isPropagationStopped())
alert("failure");
});
Here's a working version of your demo with this change
The order you bound the handlers is the order they will execute, so you need that .link handler to execute and stop the propagation before the other handler runs, checking it with event.isPropagationStopped() or event.isImmediatePropagationStopped().
This normally isn't an issue at different levels, but since .delegate() is listening on the same element, it does matter.