2. Dynamic Content

circle-info

Follow along with code examples herearrow-up-right!

Table of Contents

Key Concepts

Creating Elements

  • document.createElement(tagName) — creates a new HTML element of the specified type (e.g., 'div', 'li', 'img'). The element is not yet on the page.

  • parentElement.append(childElement) — adds an element as the last child of the parent element. You can append multiple elements at once: parent.append(el1, el2, el3).

Event Propagation & Delegation

  • Event propagation (bubbling) — when an event occurs on an element, it "bubbles up" through all of its ancestors, allowing them to also detect the event.

  • event.currentTarget — the element that the event listener is attached to (the one handling the event).

  • Event delegation — a pattern where a parent element handles events for its children, using event.target to determine which child triggered the event.

  • element.matches(selector) — returns true if the element matches the given CSS selector. Useful for filtering events in delegation.

  • element.closest(selector) — returns the nearest ancestor (or itself) that matches the selector. Useful for finding a parent container from a clicked child.

Additional Topics

  • document.querySelectorAll(selector) — returns a NodeList of all elements matching the CSS selector.

  • NodeList — an array-like collection of elements. Supports forEach and bracket notation, but not all array methods like map or filter. Use [...nodeList] to convert to a true array.

  • element.innerHTML — a property that gets or sets the HTML content inside an element. Convenient but risky with user-generated content (XSS vulnerability).

  • event.stopPropagation() — prevents an event from bubbling up to parent elements.

  • element.removeEventListener(eventType, handler) — removes an event listener. Requires a reference to the original handler function.

Why Create Elements Dynamically?

In the last lesson, we learned how to select existing elements and make them interactive. But what if the elements don't exist yet?

Consider these real-world scenarios:

  • A todo list where users can add new items

  • A shopping cart that shows items the user has added

  • A search results page that displays matches based on user input

  • A social media feed that loads new posts as you scroll

In all these cases, we can't hardcode the HTML—the content depends on data that may change. We need to create elements dynamically using JavaScript.

In this lesson, we'll first learn how to create individual elements using JavaScript, then we'll

The Create, Modify, Append Pattern

To add new elements to the page, follow this three-step pattern:

  1. Create — use document.createElement(tagName) to make a new element

  2. Modify — set its properties (textContent, id, classList, src, etc.)

  3. Append — use parentElement.append(newElement) to add it to the page

The element doesn't appear on the page until you append it. Until then, it only exists in memory.

You can also append multiple elements at once using append:

Challenge: Add a fourth list item that is highlighted.

chevron-rightSolutionhashtag

Rendering Data from Arrays

We can see the power of this create-modify-append pattern when we have an array of data that we want to generate our HTML content from.

Consider this array:

How would you use it to create this HTML structure?

First grab the ul using querySelector. Then, iterate through the list and for each item use the create-modify-append pattern:

Now consider a more complex array, like this array of movies:

How would you use this data to create the following HTML? Hint: you will need to create the image, heading, and paragraph elements AND the list item to contain them.

chevron-rightSolutionhashtag

The pattern is the same—grab the parent, iterate through the data, and for each item create-modify-append:

Making Dynamic Content Interactive

Now that we can create elements, how do we make them interactive? Let's say we want each movie to highlight when clicked.

The Problem: Adding Listeners to Dynamic Elements

One approach is to add an event listener to each element as we create it:

This works, but it is inefficient: every element has its own listener (we have three list items which means we have three separate listeners).

It would be much more efficient to have a single event listener on the parent element (the ul) and have it handle click events on any of its children.

This is possible through event bubbling (propagation) and event delegation

Event Bubbling (Propagation)

When an event occurs on an element, it doesn't just fire on that element—it "bubbles up" through all of its ancestors:

If you click the button, all three handlers fire (from innermost to outermost):

The event "bubbles up" from the button, through #inner, to #outer.

When handling a bubbled event, you can use two properties to understand what happened:

  • event.target — the element that originally triggered the event (the button)

  • event.currentTarget — the element handling the event (where the listener is attached)

Event Delegation: The Solution

Event delegation uses bubbling to our advantage. Instead of adding listeners to each child element, we add one listener to the parent and use event.target to determine which child was clicked:

The closest() method is key here—it finds the nearest ancestor (or the element itself) that matches the selector. This handles cases where the user clicks on the <h3> or <p> inside the <li>.

Benefits of event delegation:

  • Less memory — one listener instead of many

  • Cleaner code — event handling is separate from element creation

  • Works for new elements — any new <li> added to the list automatically works

You can also use element.matches(selector) to filter which elements you care about:

Challenge: Card Flip Game

Build a card flip game that lets users click cards to reveal hidden emojis. HTML and CSS has been provided for you.

Requirements:

  1. Render cards from an array of emojis (the emojis are duplicated for matching pairs)

  2. Each card should start face-down (emoji hidden) and flip to reveal the emoji when clicked

  3. Use CSS to hide/show the emoji (the class .card when face-down, and .card.flipped when flipped)

  4. Use event delegation—one click listener on the parent container handles all card clicks

When you're done rendering and adding your event listener, the card grid should look like this after clicking on two of the cards:

chevron-rightSolutionhashtag

Bonus Challenges:

  • Only allow 2 cards to be flipped at a time. If they match, keep them flipped. If not, flip them back after 1 second.

  • Add a move counter that increments each time the user flips a card

  • Add a win condition that displays a message when all pairs are matched

  • Add a "Reset" button that shuffles and resets all cards


Additional Reading

Selecting Multiple Elements with querySelectorAll

While querySelector returns the first matching element, querySelectorAll returns all matching elements as a NodeList:

A NodeList is array-like but not a true array. You can access elements by index and use forEach:

But you cannot use map, filter, or other array methods directly:

To use array methods, spread the NodeList into an array first:

innerHTML: A Shortcut with Risks

Instead of creating elements one by one, you can inject HTML directly using innerHTML:

This is more concise, but comes with serious security risks if used with user-generated content:

This is called a Cross-Site Scripting (XSS) attack. The malicious code runs in the user's browser.

Rules for innerHTML:

  • Safe: hardcoded strings that you control

  • Dangerous: any data from users, APIs, or databases

When in doubt, use createElement and textContent—they're safer because textContent escapes HTML characters automatically.

Stopping Event Propagation

Sometimes you don't want an event to bubble up to parent elements. You can stop propagation using event.stopPropagation():

When to use it:

  • When a child element should handle an event exclusively

  • When you have nested interactive elements and don't want clicks to trigger multiple handlers

When NOT to use it:

  • Don't use it "just in case"—it can break event delegation and other patterns that rely on bubbling

  • If you're using event delegation, you typically don't need stopPropagation

Removing Event Listeners

You can remove event listeners using removeEventListener. This requires a reference to the original handler function:

Important: You cannot remove an anonymous function because you have no reference to it:

When to remove listeners:

  • When an element is being removed from the page (though modern browsers handle this automatically)

  • When you want to disable interactivity temporarily

  • When implementing "one-time" actions (though you can also use { once: true } option)

Last updated