views:

29

answers:

1

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">
        <a href="google.com" class="link">link</a>
    </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:

  1. All event handlers must be delegated off of $(".top"), as I potentially have thousands of these in the page.
  2. 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: http://jsfiddle.net/CHn8x/

+2  A: 

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.

Nick Craver
A co-worker also found that solution while I was writing up the question, which seems to work. It's pretty inconvenient, because you have to modify the other handler, though. In our case, it will work for now since we have a meta-layer that wraps and attaches our click handlers, so we can do the check in the generic wrapper handler. Thanks!
bcherry
By the way, order does not seem to matter.
bcherry
@bcherry - It will if you're not using the latest jQuery, `.live()` (which `.delegate()` uses internally) didn't always respect element ordering in the document ;)
Nick Craver
the function name is pretty funny too, after attr, val, css, html. You've got stopImmediatePropagation and isImmediatePropagationStopped... :)
galambalazs
@galambalazs - They just didn't *rename* it ;) https://developer.mozilla.org/en/DOM/event.stopPropagation
Nick Craver
@Nick Craver I know it is the original name for those things, but the standard is full of long names, and jQuery has just created a shortcut for each of them, except, as we can see, event propagation methods. :)
galambalazs
@Nick Craver of course I'm using the latest, otherwise I wouldn't be using `.delegate()`. And it's `.live()` that uses `.delegate()` internally, no the other way around :) And what else are you supposed to name something like `stopImmediatePropagation`? Thanks again!
bcherry
Also, it still seems like a bug that you have to check `isImmediatePropagationStopped`. Isn't the whole point of `stopImmediatePropagatio` that it will cut out and not run the other handlers? It seems that in a correctly-implemented library, there would be no use for `isImmediatePropagationStopped`, because you'd never enter another handler, so you wouldn't need to check it.
bcherry
@bcherry - For the first comment: `.delegate()` *does* use `.live()` as I said before, it's just `.live()` with a different context, you can see the jQuery core source code here: http://github.com/jquery/jquery/blob/master/src/event.js#L875 As for the second comment, yes that's true of *normal* event handlers, but these aren't normal, you're in the *same* event handler that iterates though and checks that the target matches the selector, and if so executes the handler you specified, though in it's own closure, it's inside the same event handler of `click` on `.top`.
Nick Craver
@Nick Craver, yeah, it just seems to me that `stopImmediatePropagation()` should kill all event handlers that would have been executed next, delegated or not. Maybe there should just be another method that does.
bcherry