简体   繁体   中英

foo function is called twice

I am cloning a div tag when user click on available div. After cloning it I am inserting it after clicked div tag. This new div tag also has cross link to delete it. However when I click on cross link it does delete that particular div. It means it is working. Now I also have one function named foo . This method is called upon adding and deleting div tag. But when I click on delete link, this foo function is called twice. I know its reason because I am attaching click event to both parent of delete link and to delete link itself therefore, it is called twice. But How can I make it call once upon both adding and deleting div element?

Here my JSFIDDLE

Html:

<div class="container">
    <div class="bx"></div>    
</div>

Here is js

$('body').on('click', '.bx', function (e) {
        var clone = $(this).clone();
        clone.append('<a class="bx-del" href="#">X</a>');
        $(this).after(clone);
        foo();
});



$('body').on('click', '.bx-del', function (e) {
        e.preventDefault();
        $(this).parent().remove();
        foo();
});

function foo() {
    console.log('foo');
}

My original answer was :

Assuming .bx-del is inside .bx (better to add html as well in the question) then adding e.stopPropagation() to the .bx-del callback should work.

Eg

$('body').on('click', '.bx-del', function (e) {
    e.preventDefault();
    e.stopPropagation();
    $(this).parent().remove();
    foo();
});

Documention here

It has been pointed out that both events are actually attached to the body and run as delegated events. This solution seems to work but the question is whether or not it is reliable. The question here is in what order are delegated events run? Are they always run "depth first" or based on the order they were added or what? Running them depth first would be the most logical in my view, but performance considerations will play a large role here too.

I have been unable to find any hard documentation on this. The jQuery .on() documentation says

jQuery bubbles the event from the event target up to the element where the handler is attached (ie, innermost to outermost element) and runs the handler for any elements along that path matching the selector.

which could be interpreted as saying that, but could also be an explation of the concept of delegation.

Thus a safer solution to the the original problem, in my view, would be to combine the two lick events into one:

$('body').on('click', '.bx', function (e) {
    var t = $(e.target);
    if( t.hasClass('bx-del') || t.closest('.bx-del').length > 0){
        e.preventDefault();
        t.closest('.bx').remove();
        foo();
    } else {
        var clone = $(this).clone();
        if( $('.bx-del', clone).length == 0 ){
            clone.append('<a class="bx-del" href="#">X</a>');
        }
        $(this).after(clone);
        foo();
    }
});

(I have also fixed so that cloning a div with a delete button doesn't add a second delete button).

This is what event.stopPropagation() is for ( docs ). It prevents the event from bubbling up into the DOM tree. If you add e.stopPropagation() to your delete link click handler, it will not bubble up to the div that contains it and trigger the click handler there too:

As pointed out in the comments, this is not correct, even though it appears to work correctly. Both event handlers are attached to body, and only via a 'trick' run on specific elements. The event is already at the body element, and will therefore not bubble any further. Furthermore, event.stopPropagation() should not stop other handlers from execution, event though they seem to do that right now anyway. The correct function to use here is event.stopImmediatePropagation() ( docs at jquery ).

$('body').on('click', '.bx-del', function (e) {
        e.preventDefault();
        e.stopImmediatePropagation();
        $(this).parent().remove();
        foo();
});

Because you cannot stopPropagation to the <body> tag when the event has already propagated to the <body> tag and both your events are on the <body> tag, I would suggest just attaching the delete event handler right to the delete X object and not use propagation for that event handler. You can then stopPropagation of that click so it won't be seen by the parent at all. In your case this is fairly easy.

Working demo: http://jsfiddle.net/jfriend00/7CDNG/

$('body').on('click', '.bx', function (e) {
        var clone = $(this).clone();
        clone.append('<a class="bx-del" href="#">X</a>');
        $(this).after(clone);
        clone.find('.bx-del').on('click', function(e) {
            $(this).parent().remove();
            foo();
            // stop propagation and prevent default
            return false;
        })
        foo();
    });

function foo() {
    console.log('foo');
}

Alternatively, you could put the .bx handler on the document object and the .bx-del handler on the body object and then you could stopPropagation up to the document object from the .bx-del handler. But, it seems in this case, it is much cleaner to just attach the delete event directly to the delete object and not use delegated event handling to avoid any possible misinterpretation of the click event.

Apparently the other answers are getting away with using .stopPropagation() on one body event handler to stop the other one from firing. Here's a quote from the jQuery documentation for .stopPropagation() : "I think you're getting lucky that's all. This is taken directly from the jQuery documentation for stopPropagation(): "Note that this will not prevent other handlers on the same element from running." Since both of these event handlers are on the body object, it does not seem like a wise and safe move to use .stopPropagation() from one to stop the other from running. It's my opinion that that technique is not safe and is not the conceptually correct way of solving this problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM