Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to build a simple interactive shopping list. The goal is straightforward: when you click an item in the list, it should toggle a "completed" class to strike it through. It sounds trivial, but this is where a lot of developers run into a specific wall when their apps start growing.

The "One-by-One" Approach

My first instinct—and probably yours—is to just grab all the list items and slap a listener on each one. Let's try that.

<ul id="shopping-list">
  <li>Milk</li>
  <li>Eggs</li>
  <li>Bread</li>
</ul>

<script>
const items = document.querySelectorAll('#shopping-list li');

items.forEach(item => {
  item.addEventListener('click', () => {
    item.classList.toggle('completed');
  });
});
</script>

This works perfectly for the three items already on the page. I click "Milk," it crosses out. I click "Eggs," it crosses out. Everything seems fine. But here is where the real world hits us: our list is dynamic. We need to be able to add new items.

The Dynamic Element Dilemma

Let's add a simple input and a button to add new groceries to our list. I'll write a quick function to append a new li to the ul.

const list = document.getElementById('shopping-list');
const input = document.getElementById('item-input');
const addButton = document.getElementById('add-btn');

addButton.addEventListener('click', () => {
  const newItem = document.createElement('li');
  newItem.textContent = input.value;
  list.appendChild(newItem);
  input.value = '';
});

Now, try it out. Add "Coffee" to the list. Now click on "Coffee." Nothing happens. Why? Because the forEach loop I ran at the start only attached listeners to the elements that existed at the moment the page loaded. The new "Coffee" item was born into a world where no one told it how to react to a click.

I could technically add the event listener inside the addButton function every time I create a new element. But imagine a list with 500 items. That's 500 separate function objects sitting in memory. It's inefficient and, frankly, a pain to manage if you ever need to remove or change those listeners.

Bubbling to the Rescue

Here is the "Aha!" moment. I remember that events in JavaScript don't just hit the target and stop; they "bubble" up. If I click an li, that click event also triggers on the ul, then the body, then the document.

So, instead of putting listeners on every single item, why not put one listener on the parent ul? Let's test this theory.

const list = document.getElementById('shopping-list');

list.addEventListener('click', (event) => {
  console.log('The list was clicked!');
});

Now, whenever I click any list item—even the ones I add dynamically—the console logs "The list was clicked!" The parent is catching the event as it bubbles up. This is the core of Event Delegation.

Pinpointing the Target

The problem now is that the parent knows something was clicked, but it doesn't inherently know which item it was. If I click the gap between items or the padding of the ul, I don't want to toggle a "completed" class on the whole list.

We can use event.target to see exactly which element triggered the event. Let's refine the logic:

list.addEventListener('click', (event) => {
  // We only care if the clicked element is an LI
  if (event.target.tagName === 'LI') {
    event.target.classList.toggle('completed');
  }
});

This is much cleaner. I have one single listener managing an infinite number of potential items. It doesn't matter when the items are added or removed; the parent is always there to catch the bubble.

One last tip: if your list items get more complex (like having a span or an icon inside the li), event.target might return that inner span instead of the li. In those cases, I usually use event.target.closest('li'). It tells JavaScript: "Find the closest element that matches 'li', starting from the target and moving up the tree." It's a lifesaver for preventing bugs in more complex UIs.




📋 Practical Task

Build a Dynamic Task Board with Delegation

Create a simple Task Board application. You will need an input field and an "Add Task" button that appends tasks to a <ul>.

  • Implement Event Delegation on the <ul> so that clicking any task toggles a .done class (style it with text-decoration: line-through).
  • Each task <li> should contain a "Delete" button inside it.
  • Use the same parent event listener to handle the "Delete" button clicks. Use event.target or .closest() to identify if the delete button was clicked, and then remove that specific task from the DOM.
  • Constraint: You are not allowed to attach event listeners directly to the <li> or the delete buttons during their creation. Everything must be handled by the parent <ul>.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.