jQuery: Understanding "event.stopPropagation()" when underlying element has click event set using "find()"

StackOverflow https://stackoverflow.com/questions/18192377

Pergunta

HTML:

<ul>
    <li>
        <a href="http://wp.pl">wp.pl</a>
    </li>
</ul>

Javascript:

$(document).ready(function() {
    $body = $('body');

    $body.on('click', 'a', function(event) {
        event.preventDefault();
        event.stopPropagation();

        /**
         * Causes the "This also..." never shows, but "Why is..." still appears
         */
        //event.stopImmediatePropagation();

        alert('Should be seen always');
    });

    $body.on('click', 'a', function(event) {
        alert('This also should be seen - same level as "a"');
    });

    $body.on('click', 'li', function() {
        alert('This never shows - and that is ok - because is under "a"');
    });

    $body.find('ul').on('click', 'li', function() {
        alert('Why is this shown?');
    });
});

Fiddled here: http://jsfiddle.net/g3PEr/1/

The question is: why click event set on element found by find() fires even when its child element has stopPropagation() or stopImmediatePropagation()?


Please read accepted answer and its comments to know why is this happening.

Foi útil?

Solução

It doesn't really matter if you specify "a" or "li" in the second argument - the event handler is attached to "body". The only thing the selector does is acting as a filter. The event handler won't be fired when none of the elements on the propagation path matched the filter "a". And that's it.

So consider your code as:

   $body.on('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
    });

    $body.on('click', function(event) {

    });

    $body.on('click', function() {

    });

    $("ul").on('click', function() {

    });

Since bubbling starts at the bottom, the "ul" handler is fired first. Then comes the body handler, and they fire in the order the events were attached since they are all on body.

.stopImmediatePropagation() can be used to stop propagation to handlers that are on the same level as well as any upper levels.

The upper levels of "body" are still document and window (window being at the top of everything), but because you have no handlers there you will not observe that stopPropagation() has any effect.

Another way to explain it that calling $("body").on( "click", "li", fn ); is the same as doing:

$("body").on( "click", function(e) {
    var currentTarget = $(e.target).closest("li");
    if( currentTarget.size() > 0 ) {
        e.currentTarget = currentTarget;
        fn.call(currentTarget, e);
    }
});

Outras dicas

you are using delegated event registration to register the event handler, it makes use of event bubbling to register the event handlers means when an event happens in an element it will get bubbled upto all the ancestor elements till it reaches the document object.

in your case when you click on a, it triggers click event for the element a, then it gets propagated to li then to ul where you have a click handler registerd it gets fired, then it reaches the body, html and last to document object where you have the other click handlers registered so those also gets executed where you are stopping the propagation but already the events attached to ul has got fired.

the li handler associated with body is not fired because the propagation is prevented from the a handler in body

You are using delegated event listeners so the event handlers are attached to the delegated target, not the descendant.

The first three events are bound to the body element and the last event is bound to the ul element, which is a child of the body. So the handler on the ul element would fire first because the event hasn't bubbled to the body yet. Once the event reaches the body the propagation will be stopped (if an a tag was clicked).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top