Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
tl;dr The purpose of addEventListener is to define what happens when an event is triggered at some element. But it also allows us to implement event delegation, due to the bubbling/propagation beh...
Answer
#17: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
The event propagation _always_ runs three phases:- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached.
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`).
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root.
- During those phases, any element that has a listener for the triggered event will execute it. What changes is *when* it's executed:
- - If the element is the event's target, it's executed in the target phase.
- - For all the other elements, the third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", isn't technically accurate (because it might give the impression that the three phases are not executed), but I admit it might be enough for most people to understand the basic mechanism of event propagation.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
- The event propagation runs three phases:
- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached.
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`).
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root.
- During those phases, any element that has a listener for the triggered event will execute it. What changes is *when* it's executed:
- - If the element is the event's target, it's executed in the target phase.
- - For all the other elements, the third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", isn't technically accurate (because it might give the impression that the three phases are not executed), but I admit it might be enough for most people to understand the basic mechanism of event propagation.
- As a side note, it's worth to remind that [not all events bubble.](https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles)
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#16: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
The event propagation always runs three phases:- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached.
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`).
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root.
During those phases, any element that has a listener for the triggered event will execute it:- - If the element is the event's target, it's executed in the target phase.
- - For all the other elements, the third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
> But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", although not technically accurate, is enough for most people to understand the basic mechanism of event propagation.- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
- The event propagation _always_ runs three phases:
- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached.
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`).
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root.
- During those phases, any element that has a listener for the triggered event will execute it. What changes is *when* it's executed:
- - If the element is the event's target, it's executed in the target phase.
- - For all the other elements, the third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", isn't technically accurate (because it might give the impression that the three phases are not executed), but I admit it might be enough for most people to understand the basic mechanism of event propagation.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#15: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
- The event propagation always runs three phases:
1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`)3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document rootDuring those phases, any element that has a listener for the triggered event will execute it. The third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", although not technically accurate, is enough for most people to understand the basic mechanism of event propagation.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
- The event propagation always runs three phases:
- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached.
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`).
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root.
- During those phases, any element that has a listener for the triggered event will execute it:
- - If the element is the event's target, it's executed in the target phase.
- - For all the other elements, the third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", although not technically accurate, is enough for most people to understand the basic mechanism of event propagation.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#14: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click in any element inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if that element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Actually, it's more complicated
- The explanation above is a simplified version. What actually happens when an event is triggered is this:
- ![Event propagation](https://software.codidact.com/uploads/oafjz1wgNYob53HTHpdLHJBy)
- The event propagation always runs three phases:
- 1. Capturing phase: the event starts at the document root, and propagates down the DOM hierarchy, until the target element is reached
- 2. Target phase: the event reaches its target (the element that actually triggered the event, the one we get with `e.target`)
- 3. Bubbling phase: the event is propagated up the DOM hiearchy, starting from the target, until it reaches the document root
- During those phases, any element that has a listener for the triggered event will execute it. The third parameter of `addEventListener` that we saw above controls _when_ it'll be executed. The default is to execute it in the bubbling phase, but setting it to `true` makes it execute in the capturing phase.
- > But that's just me being pedantic. Saying that "_bubbling is when the event is propagated up_" and "_capturing is when the event is propagated down_", although not technically accurate, is enough for most people to understand the basic mechanism of event propagation.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#13: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
<span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)with a span ← The parent has such listener, but e.target will be the span, not the paragraph- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent
- with a span ← The parent executes the listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#12: Post edited
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. Then it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element (the one we've got with `e.target`) is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#11: Post edited
# tl&dr;- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl;dr
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#10: Post edited
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#9: Post edited
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- ### Bubbling
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#8: Post edited
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: A paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#7: Post edited
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
> Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`'s listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#6: Post edited
- # tl&dr;
The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation property.- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's).- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation behaviour.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's), even if the listener is assigned to the paragraph.
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#5: Post edited
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation property.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's).
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation property.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's).
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate).
#4: Post edited
`addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) that will be invoked "later".As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event).`setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here.You said in the comments that "*the passing of milliseconds is an event*", but **it's not an event _as defined in the documentation_**. Read the [relevant link](https://developer.mozilla.org/en-US/docs/Web/API/Event) for the full definition of what the language calls "Event", and [see here](https://developer.mozilla.org/en-US/docs/Web/API/Event#interfaces_based_on_event) the list of all available events. And the more important thing (again): `setInterval` and `setTimeout` **don't handle these events**. It doesn't matter how *you* (or me, or anyone else) define an "event", the language's definition is different from yours, and to write this answer I'm using the language's definition.- ---
# EventsTo fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML:- ```html
<div id="main"><div>other div</div><p id="first">first paragraph</p><p id="second">second paragraph <a href="some.site">with a link</a></p><p id="third">third paragraph <span>with a span</span></p></div>- ```
And I add an event listener to the first paragraph:- ```javascript
document.querySelector('#first').addEventListener('click', function(e) {- console.log('clicked: ' + e.target.innerText);
- });
- ```
If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to).But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this:- ```javascript
// add the same event listener to all paragraphsfor (const p of document.querySelectorAll('div#main p')) {p.addEventListener('click', function(e) {console.log('clicked: ' + e.target.innerText);});}- ```
But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text.That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`.When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`):- ```none
<p id="third"> -- this paragraph has a listener for the click eventthird paragraph -- if I click here, it triggers the event (and e.target is the paragraph)<span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event)with a span -- the parent has such listener, but e.target will be the span, not the paragraph</span></p>- ```
This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation.- ---
Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently.If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added.One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph:- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
// if element is not a paragraph, try to find the closest oneif (element.nodeName !== 'P')element = element.closest('div#main p');if (element) // paragraph found- console.log('clicked: ' + element.innerText);
else console.log("clicked element is not a paragraph, or it's not inside one");- }
- });
- ```
Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one.> To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph.And if I programatically add another paragraph inside the div:- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
I don't need to add an event listener to it. Thanks to bubbling/propagation, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.- ---
Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked.This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one.---As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while the [`load` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).
- # tl&dr;
- The purpose of [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) is to define what happens when an event is triggered at some element. But it also allows us to implement *event delegation*, due to the bubbling/propagation property.
- ---
- # Explaining the terminology
- Suppose I have this HTML:
- ```html
- <p id="text">A paragraph <span>with a span</span></p>
- ```
- Let's add an event listener to the paragraph:
- ```javascript
- document.querySelector('#text').addEventListener('click', function(e) {
- // just prints the text contents of the clicked element
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the paragraph, it _should_ print its text contents ("_A paragraph with a span_"), but the output actually depends on _where_ I click.
- If I click on the `span` (any part of the "_with a span_" text), the output will be `clicked: with a span` (it prints only the `span`'s content). If I click on any part of the "_A paragraph_" text, it prints `clicked: third paragraph with a span` (the whole paragraph's content).
- But why this happens if the event listener is assigned to the paragraph, and not to the `span`? That happens because when an event is triggered by an element, it first checks if that element has a corresponding event listener. If it doesn't, it checks if the parent element has such listener. Then it checks the parent's parent, and so on, and if any listeners are found, they're executed.
- One important detail is that the target element is the one that triggered the event, even if the listener is in another element. That's why, when I click on the `span`, it prints only the `span`'s contents (not the whole paragraph's).
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, etc) is called "**bubbling**".
- Another way of explaining:
- ```none
- <p id="text"> ← This paragraph has a listener for the click event
- A paragraph ← If I click here, it triggers the event (and e.target is the paragraph)
- <span> ← If I click inside the span, the event bubbles to the parent (because this span has no listener for the click event)
- with a span ← The parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- More interesting, if the `span` also has an event listener, both (the `span`'s and the paragraph's listeners) will be triggered:
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- });
- ```
- Now if I click on the `span`, the output will be:
- ```none
- span-only listener: with a span
- clicked: with a span
- ```
- That happens because it first checks if `span` has a listener. It does, hence it's executed. Then, the event bubbles to the `span`'s parent (the paragraph), which also has a listener, thus it's executed as well. And note again that the event's target will always be the clicked element (in this case, the `span`), regardless of the element that has the listener.
- > Side note: inside the `span`'s listener you could call [`e.stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation), which interrupts the bubbling: it prevents the event from propagating, and only the `span`' listener would be triggered.
- ---
- ### Bubbling is one type of Propagation
- "**Propagation**" is a generic term that refers to how an event "travels" (or propagates) through the DOM. Bubbling is one type of propagation - we could say it's a "bottom-up" one, because it starts on the target element and it goes "up" in the hierarchy (to the element's parent, then to the parent's parent and so on).
- But there's also a "top-down" propagation, called "**Capturing**" (it wasn't mentioned in the question, but it's directly related to the other terms and I think it's worth talking about it). It's the opposite of bubbling, because it starts in the document's root and then checks its children (and the children's children, and so on), until it reaches the target element.
- The default behaviour of `addEventListener` is to use the bubbling propagation, but you can change that by passing `true` in the third parameter (`useCapture`):
- ```javascript
- // add a listener for the paragraph
- document.querySelector('#text').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- // add a listener only for the span
- document.querySelector('#text span').addEventListener('click', function(e) {
- console.log('span-only listener: ' + e.target.innerText);
- }, true); // third parameter "true": uses capturing instead of bubbling
- ```
- Now, if I click on the `span`, it first executes the parent's listener, and the output will be:
- ```none
- clicked: with a span
- span-only listener: with a span
- ```
- Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation about the event propagation mechanism.
- ---
- ### Event Delegation
- Let's suppose we have a lot of elements that must be handled in a similar way (ex: the same listener should be used for all of them). Instead of calling `addEventListener` in each one of them, we can do that in their common ancestor. That's the whole idea of **Event Delegation**.
- Example, given the HTML below:
- ```html
- <div id="main">
- <p>first paragraph</p>
- <p>second paragraph</p>
- <h3>Not a paragraph</h3>
- </div>
- ```
- Suppose all the paragraphs must have the same event listener. Of course I could call `addEventListener` for all of them, but I could also do this:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // handle only paragraphs
- if (element.nodeName === 'P')
- console.log('clicked: ' + element.innerText);
- }
- });
- ```
- This way, if I click anywhere inside the `div` (either one of the paragraphs, or the `h3` element), it'll first check if the element has an event listener. It doesn't, but then the event bubbles to the `div`, which has a listener. Thus, it executes the function, which checks if the target element is a paragraph and do whatever it has to with it. Any other elements are ignored.
- This is also useful in situations where the paragraphs are "dynamic" (there are some scripts manipulating the main `div`, adding and removing paragraphs frequently). If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. By using event delegation (attaching a listener on the parent), all the new paragraphs will automatically have a listener.
- In another words, if I programatically add a paragraph to the `div`:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- Conclusion: this is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be better to have a specific listener for each one.
- ---
- # Further reading
- - The [documentation about events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)
- - Articles about event delegation: [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)).
#3: Post edited
`addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a function (also known as [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)), and that will be invoked "later".> Side note: `() => {}` is an *arrow function* (it's *kinda* equivalent to `function() { }` - in this case, it's a function that receives no arguments and does nothing). There are [differences between `function` and arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), but that's beyond the scope of this question.- As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event).
- `setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here.
- ---
- # Events
- To fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML:
- ```html
- <div id="main">
- <div>other div</div>
- <p id="first">first paragraph</p>
- <p id="second">second paragraph <a href="some.site">with a link</a></p>
- <p id="third">third paragraph <span>with a span</span></p>
- </div>
- ```
- And I add an event listener to the first paragraph:
- ```javascript
- document.querySelector('#first').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to).
- But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this:
- ```javascript
- // add the same event listener to all paragraphs
- for (const p of document.querySelectorAll('div#main p')) {
- p.addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- }
- ```
- But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text.
- That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`.
- When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`):
- ```none
- <p id="third"> -- this paragraph has a listener for the click event
- third paragraph -- if I click here, it triggers the event (and e.target is the paragraph)
- <span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event)
- with a span -- the parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation.
- ---
- Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently.
- If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added.
- One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // if element is not a paragraph, try to find the closest one
- if (element.nodeName !== 'P')
- element = element.closest('div#main p');
- if (element) // paragraph found
- console.log('clicked: ' + element.innerText);
- else console.log("clicked element is not a paragraph, or it's not inside one");
- }
- });
- ```
- Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one.
- > To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph.
- And if I programatically add another paragraph inside the div:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling/propagation, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- ---
- Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one.
- ---
- As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while the [`load` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).
- `addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) that will be invoked "later".
- As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event).
- `setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here.
- You said in the comments that "*the passing of milliseconds is an event*", but **it's not an event _as defined in the documentation_**. Read the [relevant link](https://developer.mozilla.org/en-US/docs/Web/API/Event) for the full definition of what the language calls "Event", and [see here](https://developer.mozilla.org/en-US/docs/Web/API/Event#interfaces_based_on_event) the list of all available events. And the more important thing (again): `setInterval` and `setTimeout` **don't handle these events**. It doesn't matter how *you* (or me, or anyone else) define an "event", the language's definition is different from yours, and to write this answer I'm using the language's definition.
- ---
- # Events
- To fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML:
- ```html
- <div id="main">
- <div>other div</div>
- <p id="first">first paragraph</p>
- <p id="second">second paragraph <a href="some.site">with a link</a></p>
- <p id="third">third paragraph <span>with a span</span></p>
- </div>
- ```
- And I add an event listener to the first paragraph:
- ```javascript
- document.querySelector('#first').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to).
- But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this:
- ```javascript
- // add the same event listener to all paragraphs
- for (const p of document.querySelectorAll('div#main p')) {
- p.addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- }
- ```
- But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text.
- That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`.
- When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`):
- ```none
- <p id="third"> -- this paragraph has a listener for the click event
- third paragraph -- if I click here, it triggers the event (and e.target is the paragraph)
- <span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event)
- with a span -- the parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation.
- ---
- Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently.
- If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added.
- One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // if element is not a paragraph, try to find the closest one
- if (element.nodeName !== 'P')
- element = element.closest('div#main p');
- if (element) // paragraph found
- console.log('clicked: ' + element.innerText);
- else console.log("clicked element is not a paragraph, or it's not inside one");
- }
- });
- ```
- Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one.
- > To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph.
- And if I programatically add another paragraph inside the div:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling/propagation, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- ---
- Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one.
- ---
- As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while the [`load` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).
#2: Post edited
- `addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a function (also known as [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)), and that will be invoked "later".
- > Side note: `() => {}` is an *arrow function* (it's *kinda* equivalent to `function() { }` - in this case, it's a function that receives no arguments and does nothing). There are [differences between `function` and arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), but that's beyond the scope of this question.
- As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event).
- `setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here.
- ---
- # Events
- To fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML:
- ```html
- <div id="main">
- <div>other div</div>
- <p id="first">first paragraph</p>
- <p id="second">second paragraph <a href="some.site">with a link</a></p>
- <p id="third">third paragraph <span>with a span</span></p>
- </div>
- ```
- And I add an event listener to the first paragraph:
- ```javascript
- document.querySelector('#first').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to).
- But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this:
- ```javascript
- // add the same event listener to all paragraphs
- for (const p of document.querySelectorAll('div#main p')) {
- p.addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- }
- ```
- But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text.
- That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`.
- When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`):
- ```none
- <p id="third"> -- this paragraph has a listener for the click event
- third paragraph -- if I click here, it triggers the event (and e.target is the paragraph)
- <span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event)
- with a span -- the parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation.
- ---
- Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently.
- If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added.
- One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // if element is not a paragraph, try to find the closest one
- if (element.nodeName !== 'P')
- element = element.closest('div#main p');
- if (element) // paragraph found
- console.log('clicked: ' + element.innerText);
- else console.log("clicked element is not a paragraph, or it's not inside one");
- }
- });
- ```
Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one- > To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph.
And if I add another paragraph inside the div:- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.- ---
- Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one.
- ---
As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while [`load`](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).
- `addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a function (also known as [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)), and that will be invoked "later".
- > Side note: `() => {}` is an *arrow function* (it's *kinda* equivalent to `function() { }` - in this case, it's a function that receives no arguments and does nothing). There are [differences between `function` and arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), but that's beyond the scope of this question.
- As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event).
- `setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here.
- ---
- # Events
- To fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML:
- ```html
- <div id="main">
- <div>other div</div>
- <p id="first">first paragraph</p>
- <p id="second">second paragraph <a href="some.site">with a link</a></p>
- <p id="third">third paragraph <span>with a span</span></p>
- </div>
- ```
- And I add an event listener to the first paragraph:
- ```javascript
- document.querySelector('#first').addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- ```
- If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to).
- But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this:
- ```javascript
- // add the same event listener to all paragraphs
- for (const p of document.querySelectorAll('div#main p')) {
- p.addEventListener('click', function(e) {
- console.log('clicked: ' + e.target.innerText);
- });
- }
- ```
- But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text.
- That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`.
- When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`):
- ```none
- <p id="third"> -- this paragraph has a listener for the click event
- third paragraph -- if I click here, it triggers the event (and e.target is the paragraph)
- <span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event)
- with a span -- the parent has such listener, but e.target will be the span, not the paragraph
- </span>
- </p>
- ```
- This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation.
- ---
- Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently.
- If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added.
- One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph:
- ```javascript
- // if I click anywhere in the main div, it calls the callback function
- document.querySelector('div#main').addEventListener('click', function(e) {
- if (e.target) {
- let element = e.target;
- // if element is not a paragraph, try to find the closest one
- if (element.nodeName !== 'P')
- element = element.closest('div#main p');
- if (element) // paragraph found
- console.log('clicked: ' + element.innerText);
- else console.log("clicked element is not a paragraph, or it's not inside one");
- }
- });
- ```
- Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one.
- > To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph.
- And if I programatically add another paragraph inside the div:
- ```javascript
- const p = document.createElement('p');
- p.innerText = 'some random text';
- document.querySelector('div#main').appendChild(p);
- ```
- I don't need to add an event listener to it. Thanks to bubbling/propagation, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents.
- ---
- Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked.
- This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one.
- ---
- As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while the [`load` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).
#1: Initial revision
`addEventListener` and `setTimeout`/`setInterval` are different things. The only thing they have in common is the fact that one of the arguments they receive is a function (also known as [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)), and that will be invoked "later". > Side note: `() => {}` is an *arrow function* (it's *kinda* equivalent to `function() { }` - in this case, it's a function that receives no arguments and does nothing). There are [differences between `function` and arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), but that's beyond the scope of this question. As you're asking about event delegation and other related terms, forget about `setTimeout` and `setInterval`. Those functions don't handle events - and when I say "events", I'm talking about [this](https://developer.mozilla.org/en-US/docs/Web/API/Event): an event is basically something that is triggered in response of some action. For exampĺe, if the user clicks on a especific element, or some script calls `element.click()`, it triggers the [click event](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event). `setTimeout` and `setInterval`, as explained in the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals), just run the callback function asynchronously, after some time has elapsed - the difference is that `setTimeout` runs the function just once, while `setInterval` runs it repeatedly (or [until it's cancelled](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)). That's all, there are no events involved here. --- # Events To fully understand how events work, I recommend you to read the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) (and also [this](https://javascript.info/event-delegation) and [this](https://davidwalsh.name/event-delegate)). But just to answer about the terminology, let's suppose I have this HTML: ```html <div id="main"> <div>other div</div> <p id="first">first paragraph</p> <p id="second">second paragraph <a href="some.site">with a link</a></p> <p id="third">third paragraph <span>with a span</span></p> </div> ``` And I add an event listener to the first paragraph: ```javascript document.querySelector('#first').addEventListener('click', function(e) { console.log('clicked: ' + e.target.innerText); }); ``` If I click on the first paragraph, the console will output "clicked: first paragraph". If I click anywhere else, nothing happens (except for the `a`, of course, because it's a link and it'll send me to the URL it points to). But what if I want to add the same behaviour to all the paragraphs? I could add the same event listener to all, like this: ```javascript // add the same event listener to all paragraphs for (const p of document.querySelectorAll('div#main p')) { p.addEventListener('click', function(e) { console.log('clicked: ' + e.target.innerText); }); } ``` But there's a problem. In the third paragraph there's a `span` inside it, so if I click on the "third paragraph" text, it'll print the whole paragraph text. But if I click on the "with a span" text, it'll output only the `span`'s text. That happens because many events, when triggered at some specific element, can "bubble"/propagate to the element's parent. In the code above, I add the event listener to all the paragraphs. But the third paragraph has a `span` inside it, so the event listener also works for this `span`. When I click the `span`, the browser checks if it has a listener for the click event. It doesn't, so the browser checks if the `span`'s parent has such listener. And the parent in this case is the third paragraph, which has a listener for the click event. So the callback functions is called, and `e.target` is the element that was clicked (in this case, the `span`): ```none <p id="third"> -- this paragraph has a listener for the click event third paragraph -- if I click here, it triggers the event (and e.target is the paragraph) <span> -- if I click inside the span, it bubbles to the parent (because this span has no listener for the click event) with a span -- the parent has such listener, but e.target will be the span, not the paragraph </span> </p> ``` This mechanism of checking an event listener in the element, and then checking the parent (and the parent's parent, and so on, until a listener is found) is called "bubbling" (or "propagation"). Check the [documentation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained) for a more detailed explanation. --- Now let's suppose that these paragraphs are "dynamic": there are some scripts manipulating the main `div`, adding and removing paragraphs frequently. If I want to keep the event listeners for all those paragraphs, it'll be very tedious to remember to add a listener every time a new paragraph is added. One simpler solution is to add a single event listener in the main `div`, and inside the callback function I check if the element clicked is a paragraph: ```javascript // if I click anywhere in the main div, it calls the callback function document.querySelector('div#main').addEventListener('click', function(e) { if (e.target) { let element = e.target; // if element is not a paragraph, try to find the closest one if (element.nodeName !== 'P') element = element.closest('div#main p'); if (element) // paragraph found console.log('clicked: ' + element.innerText); else console.log("clicked element is not a paragraph, or it's not inside one"); } }); ``` Thanks to bubbling, if I click anywhere inside the `div`, the event will be triggered. And inside the callback function I just need to check if the clicked element is a paragraph, or it's inside one > To check if the element is inside a paragraph, I use the [`closest` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest), which traverses the element and its parents, until if finds one that matches the selector. Hence, if I click on the `span`, it finds the respective `p` parent. If I click the "other div", it returns `null`, because it's not inside a paragraph. And if I add another paragraph inside the div: ```javascript const p = document.createElement('p'); p.innerText = 'some random text'; document.querySelector('div#main').appendChild(p); ``` I don't need to add an event listener to it. Thanks to bubbling, if I click on this new paragraph, the event will bubble (or propagate) to its parent (the `div`), and it'll call the callback function, which will print the paragraph's contents. --- Event delegation is the term for this whole concept of assigning event listeners to a common ancestor (relying on the bubbling/propagation behaviour), and checking which one was clicked. This is useful if the child elements share some common stuff to be done when an event happens (or when you're dinamically adding new child elements, and their listeners should be the same). But it's not a silver bullet: if each paragraph requires a completely different action, it'd be best to have a specific listener for each one. --- As a side note, the events you use are triggered when the page is loaded. The difference is that [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is fired "_when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading_", while [`load`](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) is fired "*when the whole page has loaded, including all dependent resources such as stylesheets and images*" (also, `load` event doesn't bubble).