Skip to main content

JavaScript Events

JavaScript Event Delegation

Written by Published

Event delegation puts one listener on a parent instead of many on its children.

It works because events bubble up from the element that was clicked.

The big win is that it also covers elements added later.

Example

Example

javascript

document.body.innerHTML =
  '<ul id="list"><li>A</li><li>B</li></ul>';

document.getElementById("list").addEventListener("click", function (event) {
  console.log("clicked " + event.target.textContent);
});

document.querySelectorAll("li")[1].click();

One listener on the list handles every item.

What is Delegation?

Attach one listener to a container element.

Use event.target to find out which child was actually clicked.

New children are handled automatically, with no extra listeners.

Syntax

Syntax

javascript

parent.addEventListener("click", function (event) {
  const item = event.target.closest(".item");
  if (!item) return;
});

closest handles clicks that land on a nested element.

The Problem It Solves

Without delegation, every item needs its own listener.

A hundred items means a hundred listeners, and new ones get none.

Example

Example

javascript

document.body.innerHTML = '<ul id="list"><li>A</li></ul>';

const list = document.getElementById("list");
let clicks = 0;

list.addEventListener("click", function () {
  clicks = clicks + 1;
});

const extra = document.createElement("li");
extra.textContent = "B";
list.append(extra);

extra.click();

console.log(clicks);

The new item worked immediately, with no listener of its own.

Checking What Was Clicked

A click on the container itself should usually be ignored.

Example

Example

javascript

document.body.innerHTML =
  '<ul id="list"><li class="item">A</li></ul>';

document.getElementById("list").addEventListener("click", function (event) {
  if (event.target.classList.contains("item")) {
    console.log("item clicked");
  } else {
    console.log("ignored");
  }
});

document.getElementById("list").click();
document.querySelector(".item").click();

The output is ignored then item clicked.

Using closest

If an item contains other elements, target may be one of those.

closest walks up until it finds the item itself.

Example

Example

javascript

document.body.innerHTML =
  '<ul id="list"><li class="item"><span id="label">A</span></li></ul>';

document.getElementById("list").addEventListener("click", function (event) {
  const item = event.target.closest(".item");
  console.log(item === null ? "outside" : "found the item");
});

document.getElementById("label").click();

The click landed on the span, but the item was still found.

Deciding by Action

A data attribute lets one handler serve several buttons.

Example

Example

javascript

document.body.innerHTML =
  '<div id="bar">' +
  '<button data-action="save">Save</button>' +
  '<button data-action="delete">Delete</button>' +
  '</div>';

document.getElementById("bar").addEventListener("click", function (event) {
  const action = event.target.dataset.action;
  if (action) {
    console.log("running " + action);
  }
});

document.querySelectorAll("button").forEach(function (b) { b.click(); });

The output is running save then running delete.

Removing an Item

Delegation makes a delete button on each row straightforward.

Example

Example

javascript

document.body.innerHTML =
  '<ul id="list"><li class="item">A <button class="x">X</button></li>' +
  '<li class="item">B <button class="x">X</button></li></ul>';

document.getElementById("list").addEventListener("click", function (event) {
  if (event.target.classList.contains("x")) {
    event.target.closest(".item").remove();
  }
});

document.querySelector(".x").click();

console.log(document.querySelectorAll(".item").length);

The output is 1, because the first row was removed.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript Event Delegation</title>
</head>
<body>

  <h1>Event Delegation</h1>

  <ul id="list">
    <li class="item">First</li>
    <li class="item">Second</li>
  </ul>

  <button id="add">Add an item</button>

  <p id="out"></p>

  <script>
    const list = document.getElementById("list");
    const out = document.getElementById("out");

    list.addEventListener("click", function (event) {
      const item = event.target.closest(".item");
      if (item) {
        out.textContent = "You clicked: " + item.textContent;
      }
    });

    document.getElementById("add").addEventListener("click", function () {
      const item = document.createElement("li");
      item.className = "item";
      item.textContent = "New item";
      list.append(item);
    });
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try the new items:

Add a few items and click them; they work without any extra code.

Important Points

  • One listener on a parent can handle all its children.
  • It works because events bubble.
  • event.target says which child was clicked.
  • closest handles clicks on nested elements.
  • Elements added later are handled automatically.

Conclusion

Event delegation is the standard pattern for lists and tables.

It uses fewer listeners and copes with content that changes.

Once you have used it, attaching listeners in a loop feels clumsy.