Lesson Monday

At this point, we can dynamically add Contacts to our AddressBook and see their names appear in the DOM. However, what if we want to see additional details about each Contact? In this lesson, we'll add functionality so a user can click on a contact to see additional information such as a phone number. This is similar to how an address book in a smartphone might work - you click on the name to get more information about the contact.

Our new feature will allow us to hide and show information about a specific contact. To add this functionality, we'll explore two new concepts: event bubbling and event delegation.

Event Listener Review

As we've discussed previously, we can use an event listener to "listen" for events in the DOM. That event could be anything from a click to a keystroke to hovering over an element. When an event occurs, it will trigger any event listeners that are listening for that particular event.

Up until this point, we've written jQuery that waits until the document is fully loaded (or "ready") before attaching listeners. In the UI of every application we've created, we've done the following:

$(document).ready(function() {
  // jQuery for an event listener would go here.
});

All the code inside this function will run when the document is loaded. Listeners will be attached (such as, for instance, a listener for a form submission). We wait until the document is loaded because jQuery can only attach an event listener to a DOM element if the element already exists.

However, we're going to have to do things differently when we add contacts. That's because we don't add contacts until after the document has loaded. The code in the ready() function will run as soon as the document is loaded, attaching listeners as needed - but we can't add any listeners to individual contacts because those contacts don't exist in the DOM yet.

That's why we'll need a different approach. And this approach is very important to understand if you want to be able to dynamically change things in the DOM when users do things, not just when the document has loaded.

Currently, when we create a new contact and display it in the DOM, we create a dynamic ID. We need to create an event listener that will trigger based on those dynamic IDs. In order to do this, we'll apply two concepts that are a bit more advanced - perhaps we could even call them intermediate JavaScript? Specifically, these two concepts are event bubbling and event delegation. Let's start with event bubbling.

Event Bubbling

Let's consider an example that uses an unordered list inside a div. This is for demonstration purposes only - we won't add this code to our address book!

<div id="parent-container">
  <ul id="list">
    <li id=1>1</li>
    <li id=2>2</li>
    <li id=3>3</li>
  </ul>
</div>

When we click a DOM element, it triggers an event. That event then bubbles upward. For example, when a user clicks on one of the <li>s in then code above, it triggers a click event. That click event bubbles upward to all the <li>'s parent elements in this order:

  • First, any click handlers on the specific <li> are triggered.

  • Then, any click handlers on the list <ul> are triggered. This is one level "up" from the original <li>.

  • Finally, any click handlers on the parent-container div are triggered, because it's yet another level "up" from the list <ul> element.

  • If the parent-container div was wrapped in yet another element (such as a class called yet-another-element), any event listeners attached to that div would also trigger.

If we were to do something silly like attach an event listener that listens for clicks on the <body> element, clicking anything in the body of the document would eventually trigger that click handler. That's because all the elements inside the <body> of the document eventually trigger up to <body>.

This process of hopping "upward" to higher and higher level parent elements is called event bubbling or event propagation. We could write handlers for all three of these elements and each would be triggered in the order above. But this can actually create errors for new coders, particularly if they don't mean to trigger an event on a parent element.

Event Delegation in Address Book Application

We can use event bubbling to our advantage in our address book. Remember, jQuery can only attach handlers to DOM elements that exist. Our <li> elements don't exist yet when the DOM is created. However, its parent element (ul#contacts) does! This means we can use event delegation in tandem with event bubbling to solve our problem and display details when a <li> is clicked.

Specifically, we can attach an event listener to a parent element that will fire for specified child elements. This is called event delegation. Let's add a new function to our address book to demonstrate how it works.

We'll start with a named function below our existing displayContactDetails() function:

scripts.js
...

function attachContactListeners() {

}

...

We'll then call this function as soon as the document is ready:

scripts.js
...

$(document).ready(function() {
  attachContactListeners();    // <--- This line is new!
  $("form#new-contact").submit(function(event) {
    event.preventDefault();
    const inputtedFirstName = $("input#new-first-name").val();
    const inputtedLastName = $("input#new-last-name").val();
    const inputtedPhoneNumber = $("input#new-phone-number").val();
    let newContact = new Contact(inputtedFirstName, inputtedLastName, inputtedPhoneNumber);
    addressBook.addContact(newContact);
    displayContactDetails(addressBook);
  });
});

Now we're ready to add code to this function. This function is part of our UI logic and we'll define it before $(document).ready so we don't clutter the code inside $(document).ready.

scripts.js
...

function attachContactListeners() {
  $("ul#contacts").on("click", "li", function() {
    console.log("The id of this <li> is " + this.id + ".");
  });
}

...
  • First we call jQuery's on() method on the parent element that we want to attach the event listener to. In this case, the parent element is ul#contacts. on() takes three arguments:
    • The first is the type of the event we're listening for. In our case, we want code to trigger when <li>s are clicked. We could specify other events like hover or keyup as well.
    • The second is the child element that should trigger this event listener. In this case, it's all <li>s inside ul#contacts.
    • The third is a callback that will be triggered when the event occurs.

If we load our page, populate a few contacts with our form, and click their <li>s, we'll see the id of the clicked <li> logged in the console!

event.target

In the code nested in console.log() above, this refers to the clicked li. But how does the event listener know the <li>'s id if it's attached to the parent element?

Whenever an event is triggered, it has an event.target property containing information about the element that triggered the event. event.target is JavaScript, not jQuery, but jQuery utilizes JavaScript internally.

You don't need to worry about event.target right now, but if you'd like to read more about it, check out Mozilla's event.target documentation entry. In fact, if any new concepts discussed here aren't perfectly clear yet, don't worry. You don't need to apply them on this section's independent project. However, it's a very important part of how the DOM works and will save headaches in the future.

In the next lesson, we'll create a function to show a contact's detail. We'll also add code to delete contacts to further practice with event delegation.


Example GitHub Repo for the Address Book

Terminology


  • Event Bubbling: The process of events bubbling upward when an event is triggered in the DOM.

  • Event Delegation: The process of creating an event listener on a parent element that fires for all specified child elements.

Examples


Here's an example:

<div id="top-level">
  <ul id="contacts">
    <li id=1>Contact 1</li>
    <li id=2>Contact 2</li>
    <li id=3>Contact 3</li>
  </ul>
</div>

If an li in the sample HTML above is clicked, it will first trigger any listeners on li, then listeners on #contacts, then listeners on #top-level.

function attachContactListeners() {
  $("ul#contacts").on("click", "li", function() {
    console.log("The id of this <li> is" + this.id + ".");
  });
}

"ul#contacts" is the parent element. All li elements within the parent element will be triggered on "click".

Example GitHub Repo for the Address Book

Lesson 19 of 32
Last updated April 6, 2021