Lesson Monday

We can now dynamically add Contacts to our AddressBook and see their names appear in the DOM. Great!

However, what if we want to see additional details about each Contact? Similar to how address books in our smartphones and other electronic tools work, it would be nice if we could click a contact's name to hide/show extra information. Let's tackle this next! To do this we'll explore two new concepts: event bubbling and event delegation.

Event Listener Review

As we know, we use an event listener to trigger an action when a DOM element is interacted with. It "listens" for an specified event (such as a click or form submission). When that event happens, code associated with the listener is executed. This should be review.

However, up until this point we've written jQuery that waits until the document is fully loaded (or "ready") before attaching listeners. That's what this method we've been using does:

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

We add this because jQuery can only attach an event listener to a DOM element if the element already exists. So we must ensure the page is fully loaded (or "ready") before adding any listeners.

...But this creates a problem in our address book! We just decided we wanted to display additional details when a Contact's <li> is clicked. But these <li> elements don't actually exist when our page first loads! The user must complete and submit the form to populate them.

So how do we trigger an event to display more contact details when an <li> is clicked, if that <li> doesn't exist upon first load?! With two more advanced concepts: event bubbling and event delegation. Let's start with event bubbling.

Event Bubbling

Let's consider an example, starting with an unordered list inside a div. (This is for demonstration purposes only, and we will not 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 <li> are triggered.

  • Then, any click handlers on the list <ul> are triggered, because it's one level "up" from the original <li>.

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

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 the Address Book

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. But 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();
    var inputtedFirstName = $("input#new-first-name").val();
    var inputtedLastName = $("input#new-last-name").val();
    var inputtedPhoneNumber = $("input#new-phone-number").val();
    var newContact = new Contact(inputtedFirstName, inputtedLastName, inputtedPhoneNumber);
    addressBook.addContact(newContact);
    displayContactDetails(addressBook);
  })
})

Let's add our code to the new function now. (Note that this function is part of our UI logic and that we'll define it before $(document).ready. That way, 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 upon the parent element that we want to attach the event listener to. In this case, ul#contacts. on() takes two 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, but 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.

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?

Well, whenever an event is triggered it has an event.target property containing information about the element that triggered the event. event.target is also 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 Friday's 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