Event delegation is based on the concept of event bubbling.
As we recall, "event bubbling" happens within a nested element environment, where you may have separate events assigned to both the parent element, and the child element(s). The event bubbling effect comes when the child event fires, and then the process "bubbles" up to the parent event, which then fires. Refer to my Event Bubbling page for more information.
With Event Delegation, instead of attaching an event to the parent element, and then one to every child element, we can attach a single listener to the parent element. This listener, can then manage all of the events that bubble up from its child elements. Event Delegation is basically a pattern to handle events efficiently.
Let's use our Tweet Form from our JavaScript Form Events Lesson to explain. As we recall, the user just enters a "User Name" and "Tweet" into the form. Js then processes the data and adds the new tweet to the page. The Tweet Form is comprised of an HTML form element with an id of tweetForm.
Notice we have also assigned the text inputs with id's, userName and tweet respectively. These three id's are used in the Js to create the objects for the processing of these elements:
Once the user inputs the data for the new tweet, Js will process the data and insert the new tweet into a list item (<li>) that it will have to create, within the Js processing. What this means is that we will need an empty unordered list (<ul>) to provide a place for the new tweet <li> to be inserted.
HTML
<ul id="tweets">
<p>click on an any existing Tweet to DELETE it.</p>
</ul>
and we will also have to create an object in our js to reference this <ul>:
We now have the four objects needed to process the user inputs, insert that data into an <li>, and then insert the <li> into the #tweets<ul>.
userName // represents user name input
and tweet // represents user tweet input
are the objects that represent the user input data. It is beyond the scope of this discussion to explain in detail how this user data is converted into an <li> but you can reference the Form Events Lesson for the details. The result of the Js processing for this user data is:
<li>userName - tweet></li>
And finally, the most important part, for the point of this discussion.
Our last object that we created:
tweetsContainer // represents the <ul> parent container, that will contain the new tweet <li>
becomes a clearly defined parent container where we can now use event delegation to effect the child elements, (the individual child <li>'s contained within the parent <ul>).
Tweet Form
Current Tweets:
click on trash to DELETE tweet.
As stated in our tweet form, you can "click on an existing tweet to delete it". But how do we accomplish this?
Easy, Peasy. We're going to use event delegation. Since we have a clearly defined parent container, we can use it to propagate events down to the child elements, (the child <li>'s). Because we add a new tweet as a list item (<li>), we can add the following "event" to our parent container (<ul>)(identified by the id=tweetsContainer) to remove it.
*Notice that we are using the property remove() to remove an existing tweet, and that target is a property of the evtevent object.
tweetsContainer.addEventListener('click', function (evt) {
This is a very good example of how event propagation works. We are assigning the click event to the parent object tweetsContainer, and by targeting the child object, we can effect our change.
So our event is a click event on the parent <ul> element (with the id=tweetsContainer). And from there, we target the child <li> that we clicked on for removal. So we only need one event attached to the parent element, and we can targetany of it's children for processing.
performs the removal of the <li> that the user clicked on.
What this is saying is that if the target we clicked on is an <li>, then remove that target.
Notice that we are using the event object of the callback function. and that we have assigned it an identifier of evt.
But what does target mean? What is a nodeName? And why do we need them? And why don't we just use:
evt.target.remove();
Well, if we use:
evt.target.remove();
we will remove any child element that we click on, from within the parent <ul> element. As you may have noticed, our blank <ul> "tweets" that we created in the HTML markup is not entirely blank. We also included one <p> element which gives notice that the user can click on a tweet to delete it. If we use:
evt.target.remove();
then the user could delete our instructional paragraph, and we don't want that. So we need to be more specific in our child targeting.
Because we added console.log(evt); to the event callback function(as shown above), when we click on a tweet for deletion, we could then look in the console to see the properties for evt, (the event object) of the child element that we are targeting (clicked on).
And what the console shows is a mouse event. And as we look though the properties of this event, we find a property called target and this target will have a value. If you click on our paragraph to delete it, you will see a target value of p and if you expand the target properties, you will find that this target has a nodeName of "P". And using the nodeName is a very specific way of doing the targeting.
and with that, if we click on any child target where target.nodeName === 'LI', the the child will delete, but any other target.nodeName will not. So we can delete any current tweet that we may want to, but not the informative paragraph that we added in the HTML Markup.
codefix for formatting update
*Note: because we have added both <b> and <i> attributes to the "User Name", so that when the tweet is displayed, we get a bold/italic User Name followed by a dash and the tweet,
User Name - Tweet
we have now inadvertently added a bug where if you click on the user name for deletion, the tweet will not delete. If however, you click on the tweet itself, then it will delete.
The reason for this is, that if you open the console and look at the properties for the event, when you click on a tweet userName, you will see that the target value is i, and the target.nodeName is "I". So we are trying to target an <li> for deletion, but when you click on the bolded, italicized userName, you are actually targeting an <i> element. And since there is also a <b> element involved, you might actually be targeting that, instead of th <li> that we want to be targeting.
So, we now need to slightly modify our JavaScript processing as follows:
tweetsContainer.addEventListener('click', function (evt) {
So when we factor in our bold and italic userName, with the modified code, we are saying:
"if we click anywhere within the child <li>, (either the userName or the tweet text), REGARDLESS of whether we clicked on an <li>, <b> or <i> portion of the userName, then delete the entire child <li>, which contains the entire tweet data.
*note: this will also NOT delete anything other than the closest <li>, so the user can not delete the informative <p> element we add into the "tweets" <ul>.
And now our delete function works properly, irregardless of where we click on the tweet.