quiz/js: update event bubbling and delegation quiz solution (#440)

This commit is contained in:
Nitesh Seram 2024-06-04 14:37:24 +05:30 committed by GitHub
parent cdd4d2ba0b
commit f2b326c982
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 233 additions and 6 deletions

View File

@ -2,6 +2,18 @@
title: Describe event bubbling
---
## TL;DR
**Event bubbling** is a DOM event propagation mechanism where an event, like a click, starts at the target element and bubbles up to the root of the document. This allows ancestor elements to also respond to the event.
**Practical Use:** Event bubbling is essential for event delegation, where a single event handler manages events for multiple child elements, enhancing performance and code simplicity.
**Pitfall:** Failing to manage event propagation can lead to unintended behavior, such as multiple handlers firing for a single event.
---
## What is Event Bubbling?
Event bubbling is a propagation mechanism in the DOM (Document Object Model) where an event, such as a click or a keyboard event, is first triggered on the target element that initiated the event and then propagates upward (bubbles) through the DOM tree to the root of the document.
## Bubbling phase
@ -28,18 +40,154 @@ child.addEventListener('click', () => {
});
```
When you click the "Click me!" button, both the child and parent event handlers will be triggered due to event bubbling.
When you click the "Click me!" button, both the child and parent event handlers will be triggered due to the event bubbling.
## Stopping propagation
Event propagation can be stopped during the bubbling phase using the `stopPropagation()` method. If an event handler calls `stopPropagation()`, it prevents the event from further bubbling up the DOM tree, ensuring that only the handlers of the elements up to that point in the hierarchy are executed.
```js
child.addEventListener('click', (event) => {
console.log('Child element clicked');
event.stopPropagation();
});
```
## Event delegation
Event bubbling is the basis for a technique called "event delegation", where you attach a single event handler to a common ancestor of multiple elements and use event delegation to handle events for those elements efficiently. This is particularly useful when you have a large number of similar elements, like a list of items, and you want to avoid attaching individual event handlers to each item.
```js
parent.addEventListener('click', (event) => {
if (event.target && event.target.id === 'child') {
console.log('Child element clicked');
}
});
```
## References
## Benefits
- **Cleaner Code:** Reduced number of event listeners improves code readability and maintainability.
- **Efficient Event Handling:** Minimizes performance overhead by attaching fewer listeners.
- **Flexibility:** Allows handling events happening on child elements without directly attaching listeners to them.
## Pitfalls
- **Accidental Event Handling:** Be mindful that parent elements might unintentionally capture events meant for children. Use `event.target` to identify the specific element that triggered the event.
- **Event Order:** Events bubble up in a specific order. If multiple parents have event listeners, their order of execution depends on the DOM hierarchy.
- **Over-delegation:** While delegating events to a common ancestor is efficient, attaching a listener too high in the DOM tree might capture unintended events.
## Use Cases
Here are some of the practical use cases with code examples.
### Reducing Code with Event Delegation
Imagine you have a product list with numerous items, each with a "Buy Now" button. Traditionally, you might attach a separate click event listener to each button:
```js
// HTML:
// <ul id="product-list">
// <li><button id="item1-buy">Buy Now</button></li>
// <li><button id="item2-buy">Buy Now</button></li>
// </ul>
const item1Buy = document.getElementById("item1-buy");
const item2Buy = document.getElementById("item2-buy");
item1Buy.addEventListener("click", handleBuyClick);
item2Buy.addEventListener("click", handleBuyClick);
// ... repeat for each item ...
function handleBuyClick(event) {
console.log("Buy button clicked for item:", event.target.id);
}
```
This approach becomes cumbersome as the number of items grows. Here's how event bubbling can simplify things:
```js
// HTML:
// <ul id="product-list">
// <li><button id="item1-buy">Buy Now</button></li>
// <li><button id="item2-buy">Buy Now</button></li>
// </ul>
const productList = document.getElementById("product-list");
productList.addEventListener("click", handleBuyClick);
function handleBuyClick(event) {
// Check if the clicked element is a button within the list
if (event.target.tagName.toLowerCase() === "button") {
console.log("Buy button clicked for item:", event.target.textContent);
}
}
```
By attaching the listener to the parent (`productList`) and checking the clicked element (`event.target`) within the handler, you achieve the same functionality with less code. This approach scales efficiently as you add more items.
### Dropdown Menus
Consider a dropdown menu where clicking anywhere on the menu element (parent) should close it. With event bubbling, you can achieve this with a single listener:
```js
// HTML:
// <div id="dropdown">
// <button>Open Menu</button>
// <ul>
// <li>Item 1</li>
// <li>Item 2</li>
// </ul>
// </div>
const dropdown = document.getElementById("dropdown");
dropdown.addEventListener("click", handleDropdownClick);
function handleDropdownClick(event) {
// Close the dropdown if clicked outside the button
if (event.target !== dropdown.querySelector("button")) {
console.log("Dropdown closed");
// Your logic to hide the dropdown content
}
}
```
Here, the click event bubbles up from the clicked element (button or list item) to the `dropdown` element. The handler checks if the clicked element is not the button and closes the menu accordingly.
### Accordion Menus
Imagine an accordion menu where clicking a section header (parent) expands or collapses the content section (child) below it. Event bubbling makes this straightforward:
```js
// HTML:
// <div class="accordion">
// <div class="header">Section 1</div>
// <div class="content">Content for Section 1</div>
// <div class="header">Section 2</div>
// <div class="content">Content for Section 2</div>
// </div>
const accordion = document.querySelector(".accordion");
accordion.addEventListener("click", handleAccordionClick);
function handleAccordionClick(event) {
// Check if clicked element is a header
if (event.target.classList.contains("header")) {
const content = event.target.nextElementSibling;
content.classList.toggle("active"); // Toggle display of content
}
}
```
By attaching the listener to the `accordion` element, clicking on any header triggers the event. The handler checks if the clicked element is a header and toggles the visibility of the corresponding content section.
## Further reading
- [MDN Web Docs on Event Bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)
- [JavaScript.info - Bubbling and Capturing](https://javascript.info/bubbling-and-capturing)
- [W3C DOM Level 3 Events Specification](https://www.w3.org/TR/DOM-Level-3-Events/#event-flow)

View File

@ -2,6 +2,16 @@
title: Explain event delegation
---
## TL;DR
**Event delegation** is a JavaScript design pattern where a single event listener is attached to a common ancestor to manage events for multiple child elements efficiently.
**Practical Use:** It's essential for handling events on dynamic or numerous elements, such as list items, without attaching individual listeners to each.
**Benefit:** Event delegation improves performance, reduces memory usage, and simplifies code maintenance.
**Pitfall:** Incorrectly identifying the event target can lead to unintended behavior.
---
Event delegation is a design pattern in JavaScript used to efficiently manage and handle events on multiple child elements by attaching a single event listener to a common ancestor element. This pattern is particularly valuable in scenarios where you have a large number of similar elements, such as list items, and want to streamline event handling.
## How Event Delegation works
@ -43,11 +53,80 @@ In this example, a single click event listener is attached to the `<ul>` element
Event delegation is commonly used in scenarios like:
- Managing lists, menus, or tables with many items or rows.
- Handling dynamic content in single-page applications.
- Simplifying code by avoiding the need to attach and remove event listeners for elements that change.
### Managing lists, menus, or tables with many items or rows
## Resources
```js
// HTML:
// <ul id="item-list">
// <li>Item 1</li>
// <li>Item 2</li>
// <li>Item 3</li>
// </ul>
const itemList = document.getElementById('item-list');
itemList.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log(`Clicked on ${event.target.textContent}`);
}
});
```
In this example, a single click event listener is attached to the `<ul>` element. When a click event occurs on an `<li>` element, the event bubbles up to the `<ul>` element, where the event listener checks the target's tag name to identify which list item was clicked.
### Handling dynamic content in single-page applications
```js
// HTML:
// <div id="button-container">
// <button>Button 1</button>
// <button>Button 2</button>
// </div>
// <button id="add-button">Add Button</button>
const buttonContainer = document.getElementById('button-container');
const addButton = document.getElementById('add-button');
buttonContainer.addEventListener('click', (event) => {
if (event.target.tagName === 'BUTTON') {
console.log(`Clicked on ${event.target.textContent}`);
}
});
addButton.addEventListener('click', () => {
const newButton = document.createElement('button');
newButton.textContent = `Button ${buttonContainer.children.length + 1}`;
buttonContainer.appendChild(newButton);
});
```
In this example, a click event listener is attached to the `<div>` container. When a new button is added dynamically and clicked, the event listener on the container handles the click event.
### Simplifying code by avoiding the need to attach and remove event listeners for elements that change
```js
// HTML:
// <form id="user-form">
// <input type="text" name="username" placeholder="Username">
// <input type="email" name="email" placeholder="Email">
// <input type="password" name="password" placeholder="Password">
// </form>
const userForm = document.getElementById('user-form');
userForm.addEventListener('input', (event) => {
const { name, value } = event.target;
console.log(`Changed ${name}: ${value}`);
});
```
In this example, a single input event listener is attached to the form element. It handles input changes for all child input elements, simplifying the code by avoiding multiple event listeners.
## Pitfalls
- **Incorrect Target Handling:** Ensure correct identification of the event target to avoid unintended actions.
- **Event Overhead:** While event delegation is efficient, handling complex logic within the event listener can introduce overhead if not managed properly.
## Further reading
- [MDN Web Docs on Event Delegation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation)
- [JavaScript.info - Event Delegation](https://javascript.info/event-delegation)