What You Need To Know About JavaScript Event Propagation

Posted by TotalDC

JavaScript Event Propagation is a mechanism that defines how events propagate or travel through the DOM tree to arrive at its target and what happens to it afterward.

For example, imagine that you have a click event handler on a hyperlink element that is nested inside a paragraph. If you click that link the handler will be executed. But if you assign the click event handler to the paragraph that the link is contained in, then if you click the link, it will still trigger the handler. That’s because events don’t just affect the target element that generated the event – they “travel” up and down through the DOM tree to reach their target. This is known as event propagation. In modern browsers event propagation proceeds in two phases: capturing, and bubbling phase.

The concept of event propagation was introduced to deal with situations in which multiple elements in the DOM hierarchy with a parent-child relationship have event handlers for the same event, such as a mouse click.

Event Capturing In JavaScript

To put it simply – in the capturing phase, events propagate from the Window down through the DOM tree to the target node. For example, if you click a hyperlink, that click event would pass through the element, the element, and the element containing the link.

Also if any parent of the target element and the target itself has a specially registered capturing event listener for that type of event, those listeners are executed during this phase. Here’s an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Capturing Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showTagName() {
        alert("Capturing: "+ this.tagName);
    }
    
    let elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showTagName, true);
    }
</script>
</body>
</html>

Event capturing only works with event handlers registered with the addEventListener() method when the third argument is set to true. The traditional method of assigning event handlers, like using onclick, onmouseover, etc. won’t work here. You can check out the JavaScript event listeners article to learn more about event listeners.

Event Bubbling In JavaScript

The bubbling phase in JavaScript event propagation is the exact opposite of what we talked about previously. In this phase event propagates or bubbles back up the DOM tree, from the target element up to the Window, visiting all of the ancestors of the target element one by one. For example, if you click on a hyperlink, that click event will pass through the element containing the link, the element, the element, and the document node.

If any parent of the target element and the target itself has event handlers assigned for that type of event, those handlers are executed during this phase. In modern browsers, all event handlers are registered in the bubbling phase, by default. Here’s an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Bubbling Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('Bubbling: ' + this.tagName)">DIV
    <p onclick="alert('Bubbling: ' + this.tagName)">P
        <a href="#" onclick="alert('Bubbling: ' + this.tagName)">A</a>
    </p>
</div>
</body>
</html>

Event bubbling works for all handlers, regardless of how they are registered – using onclick or addEventListener() (unless they are registered as capturing event listeners). That’s why the term event propagation is often used as a synonym for event bubbling.

How To Access The Target Element In JavaScript

The target element is the DOM node that has generated the event. For example, if you click on a link, the target element is the link.

The target element is accessible as event.target, it doesn’t change through the event propagation phases. Also, this keyword represents the current element. Here’s an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Target Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;			
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    // Selecting the div element
    let div = document.getElementById("wrap");

    // Attaching an onclick event handler
    div.onclick = function(event) {
        event.target.style.backgroundColor = "lightblue";

        // Let the browser finish rendering of background color before showing alert
        setTimeout(() => {
            alert("target = " + event.target.tagName + ", this = " + this.tagName);
            event.target.style.backgroundColor = ''
        }, 0);
    }
</script>
</body> 
</html>

The arrow (=>) sign used in the example is an arrow function expression. It has a shorter syntax than a function expression, and it would make this keyword behave properly.

How To Stop The Event Propagation In JavaScript

You can stop event propagation in the middle if you want to prevent any ancestor element’s event handlers. Here’s an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert() {
        alert("You clicked on: "+ this.tagName);
    }

    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

If you click on any child element, the event handler on parent elements is also executed and you may see multiple alert boxes.

To prevent this situation you can stop the event from bubbling up the DOM tree using the event.stopPropagation() method. Here’s how it looks:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stop Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert(event) {
        alert("You clicked on: "+ this.tagName);
        event.stopPropagation();
    }

    let elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

Also, you can even prevent any other listeners attached to the same element for the same event type from being executed using the stopImmediatePropagation() method.

Here’s an example where multiple listeners are attached to the link, but only one listener for the link will execute when you click the link, and you will see only one alert:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stop Immediate Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('You clicked on: ' + this.tagName)">DIV
    <p onclick="alert('You clicked on: ' + this.tagName)">P
        <a href="#" id="link">A</a>
    </p>
</div>

<script>
    function sayHi() {
        alert("Hi, there!");
        event.stopImmediatePropagation();
    }
    function sayHello() {
        alert("Hello World!");
    }
    
    // Attaching multiple event handlers to hyperlink
    let link = document.getElementById("link");
    link.addEventListener("click", sayHi);  
    link.addEventListener("click", sayHello);
</script>
</body>
</html>

How To Prevent The Default Action In JavaScript

Some events have a default action associated with them. For example, if you click on a link browser takes you to the link’s target, when you click on a form submit button browser submits the form, etc. You can prevent such default actions with the preventDefault() method of the event object. Here’s an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Prevent Default Demo</title>
</head>
<body>
<form action="/examples/html/action.php" method="post" id="users">
    <label>First Name:</label>
    <input type="text" name="first-name" id="firstName">
    <input type="submit" value="Submit" id="submitBtn">
</form>

<script>
    let btn = document.getElementById("submitBtn");
    
    btn.addEventListener("click", function(event) {
        let name = document.getElementById("firstName").value;
        alert("Sorry, " + name + ". The preventDefault() won't let you submit this form!");
        event.preventDefault(); // Prevent form submission
    });
</script>
</body>
</html>