Saturday, August 9, 2014

jQuery prevent event propagation and stopEventPropagation explained

Yesterday I was implementing (AGAIN) a custom dialog-popup box for a wordpress theme so I choose to go for a javascript class to handle all the stuff, with the help of jQuery.

 While writing the close dialog code I fell in a black out moment and I wasn't able to remember what was the name of the jQuery function to prevent click propagation from childrem DOM elements to parent. Of course my (perfectly looking) dialog was firing the onClick event of the parent even when the dialog content was clicked.

While doing my usual jQuery website digging I found that a lot of people struggle with the problem of click propagation, even if the jQuery library has a stopPropagation function that SOUNDS perfect for that task....but...



Let's give a look at my code :


<div id="dialog-background">
    <div id="dialog-content">
    Some fancy stuff to display.
    </div>
</div>

So simple, I had a dialog-background div that is used to cover the whole page while the dialog is shown, and a dialog-content where my dialogish stuff goes. All this code is generated on the fly with a javascript function and added dinamically to the page DOM tree.

I want dialog to close when the user clicks on the background, so after inserting the divs I bind an onClick function to the background div.


$('#dialog-background').on('click',function() {
    myDialog.hide();  //this function removes all the dialog DOM elements
});

But... Huston we have a problem! This way by clicking on the container element the event propagates to the parent elements too. So clicking the inner div makes my dialog to close. Here comes the stopPropagation function....or better to say it should.

Let's see some new code :


$('#dialog-background').on('click',function(event) {
    event.stopPropagation();  //this should stop propagation
    myDialog.hide();  //this function removes all the dialog DOM elements
});

I can hear some of you already screaming at the error, but let's explain why I wrote it that way.
This is a very common mistake. (just dig stackoverflow to see)

We want the background div to close when someone clicks it, so we wrote a click handler for that div. And we all know that by clicking the inner div that holds the content the event will propagate to parents.

This is correct. The mistake is that by inserting the event.stopPropagation call inside the background div we are preventing background's parents to receive the click event, not background itself.

So we have two solutions for that, one is to add a second click handler on the content div and put there the event.stopPropagation call. But hey...what if we have more than one inner, non nested, elements? Let's imagine our dialog with a title span, a content div and some buttons? Should we need to attach an onClick handler to every inner element? Not that elegant.

So my solution, wich I personally find more clean is as follows :


$('#dialog-background').on('click',function(event) {
    if(event.target === this)
        myDialog.hide();  
});

 Here comes in help the event.target property. It holds a reference to the DOM element who initiated the click event. This way we can check if the click has happened on the background div and discard every other source, without the need to create a custom handler for every internal div.

1 comment:

  1. awesome !!. I was having the same problem when implementing modal

    ReplyDelete