Courses Learn Blog Contact
Get Started →
Lesson 4

Day 4: The DOM and Events

Selecting, changing and building the page from data, handling clicks and form submissions, and using event delegation so rows added later still work.

Webbo3 Level 2: JavaScript Development 16 min read Free

Webbo3 Level 2 · JavaScript Development · Day 4 of 20

The DOM and Events

The day your JavaScript stops printing to a console and starts changing the page in front of a real person.

By the end of today you can

  • Explain what the DOM is, and why a refresh undoes your changes
  • Select any element on a page and change its text, classes or styles
  • Build HTML from an array of data and put it on the page
  • Respond to a click, a typed character and a submitted form
  • Use event bubbling deliberately, with one listener instead of fifty
  • Create elements safely, so user text can never become code

Everything you have written so far has been invisible. It ran, it printed to a console only you can see, and the page never changed.

Today that ends. The DOM is the bridge between your JavaScript and the page, and crossing it is the single biggest jump in this course.

1. What the DOM Actually Is

Definition

The DOM (Document Object Model) is the live tree of objects the browser builds from your HTML. There is one object for every element, and the browser draws the page from that tree.

Real-Life Example

Think of an architect's paper plan and the actual building. Your HTML file is the plan. The DOM is the building the browser constructs from it. JavaScript works on the building: it can repaint a wall or add a room, and people see the change immediately. The paper plan never changes.

That is why a refresh undoes everything JavaScript did: the browser throws the building away and builds a fresh one from the plan.

See the Tree for Yourself

Open DevTools (F12) and look at the Elements tab. That is not your file: it is the live tree. Double-click any text in it and change it. The page updates instantly, your file does not, and a refresh puts it back.

Picture This

Picture the building and its plan. JavaScript is a builder who can change the building while people are inside it. Refresh the page and the building is knocked down and rebuilt from the untouched plan.

2. Selecting Elements

Definition

Before you can change an element, you have to get hold of it. Selecting means asking the DOM for an element using a CSS selector, exactly the selectors you wrote in your CSS last month.

Real-Life Example

In a crowded hall, you can call "the person in the red shirt" (the first one who matches) or "everyone in a red shirt" (all of them). Selectors are how you point at elements the same way.

Types of Selecting

MethodGives youIf nothing matches
document.querySelector(selector)The first matching elementnull
document.querySelectorAll(selector)Every match, as a list you can loop overAn empty list

Type 1: querySelector

What this code doesWe want to grab the heading on a page by its id and read its text, to prove JavaScript can reach into the page.
index.html
1
2
<h1 id="greeting">Hello, student</h1>
<p>Welcome to the dashboard.</p>
app.js
1
2
3
4
5
// Find the FIRST element that matches the selector #greeting
const heading = document.querySelector('#greeting');

// Read the text inside it
console.log(heading.textContent);
BROWSERindex.html
CONSOLE   BROWSER DEVTOOLS
Hello, student

Type 2: querySelectorAll

What this code doesWe want every card on the page at once, so we can count them and read each one.
index.html
1
2
3
<div class="card">HTML</div>
<div class="card">CSS</div>
<div class="card">JavaScript</div>
app.js
1
2
3
4
5
6
7
8
9
10
// Find EVERY element with the class card
const cards = document.querySelectorAll('.card');

// How many did we find?
console.log(cards.length);

// Read the text of each one
cards.forEach((card) => {
  console.log(card.textContent);
});
BROWSERindex.html
CONSOLE   BROWSER DEVTOOLS
3
HTML
CSS
JavaScript

Common Mistake: The Script Runs Too Early

The browser reads your HTML from top to bottom. If your script runs before the elements below it have been built, the selector finds nothing and gives back null.

What this code doesWe want to see what happens when a script tries to use an element that the browser has not built yet.
index.html
1
2
3
4
5
6
7
<script>
  // This runs BEFORE the h1 below exists
  const heading = document.querySelector('#greeting');
  console.log(heading.textContent);
</script>

<h1 id="greeting">Hello, student</h1>
BROWSERindex.html
CONSOLE   BROWSER DEVTOOLS
Uncaught TypeError: Cannot read properties of null (reading 'textContent')
The Fix Cannot read properties of null means the selector found nothing. Nine times out of ten the script ran too early. Put your <script> at the very end of <body>, or add the defer attribute to it in the <head>.
What this code doesWe want the script to load in the head but run only once every element exists.
index.html
1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
  <head>
    <title>My page</title>
    <!-- defer: wait until the page is built before running -->
    <script src="app.js" defer></script>
  </head>
  <body>
    <h1 id="greeting">Hello, student</h1>
  </body>
</html>
NOTE   
This file is read by the browser. With defer, app.js runs after the body has been built, so every selector finds its element.
Picture This

Picture calling out for someone who has not arrived at the party yet. Nobody answers. defer is waiting until everyone is inside before you start calling names.

3. Changing What Is on the Page

Definition

Once you have an element, you change the page by changing the element's properties: its text, its classes or its styles. The browser redraws the page straight away.

Real-Life Example

A scoreboard at a football match. The board is always there; the operator changes the numbers on it and switches lights on and off. Nobody builds a new scoreboard for every goal.

Types of Change

You writeIt doesUse it for
el.textContent = xReplaces the text, as plain textAlmost always
el.innerHTML = xReplaces the contents, reading x as HTMLOnly markup you wrote yourself
el.classList.add(c)Adds a classTurning a CSS style on
el.classList.remove(c)Removes a classTurning it off
el.classList.toggle(c)Adds it if missing, removes it if presentMenus, dark mode, accordions
el.style.color = xSets one inline styleRarely: prefer a class

Type 1: Text

What this code doesWe want to replace the text of a heading and a badge, the way a dashboard greets a user who has just logged in.
index.html
1
2
<h1 id="greeting">Welcome</h1>
<span id="badge">0 new</span>
app.js
1
2
3
4
5
6
7
// Get hold of the two elements
const heading = document.querySelector('#greeting');
const badge = document.querySelector('#badge');

// Replace their text
heading.textContent = 'Welcome back, Chidinma';
badge.textContent = '3 new';
BROWSERindex.html

Type 2: Classes

The clean way to change how something looks is to write the look in CSS as a class, and let JavaScript switch the class on and off.

What this code doesWe want to switch a badge into its "active" look by adding a class, keeping the styling in CSS where it belongs.
index.html
1
<span id="badge">3 new</span>
style.css
1
2
3
4
5
6
.is-active {
  background-color: #0a6e6e;
  color: white;
  padding: 2px 10px;
  border-radius: 10px;
}
app.js
1
2
3
4
5
// Get the badge
const badge = document.querySelector('#badge');

// Turn the active style on
badge.classList.add('is-active');
BROWSERindex.html

Type 3: Inline Style

What this code doesWe want to change one style directly from JavaScript, for the rare case where a class is not practical.
index.html
1
<h1 id="greeting">Welcome back</h1>
app.js
1
2
3
4
5
// Get the heading
const heading = document.querySelector('#greeting');

// Set its colour directly
heading.style.color = '#0a6e6e';
BROWSERindex.html
Common Mistake: innerHTML With Text From a User innerHTML reads whatever you give it as HTML. If that text came from a user, they can type tags and the browser will obey them. textContent always shows text as text.
What this code doesWe want to put the same user input on the page two ways and compare what the browser does with it.
index.html
1
2
<p>With textContent: <span id="safe"></span></p>
<p>With innerHTML: <span id="unsafe"></span></p>
app.js
1
2
3
4
5
6
7
8
// Pretend a user typed this into a form
const typed = '<b>bold</b>';

// Safe: shown exactly as typed
document.querySelector('#safe').textContent = typed;

// Unsafe: the browser treats it as HTML
document.querySelector('#unsafe').innerHTML = typed;
BROWSERindex.html

Here it only made the text bold. A user could type a tag that runs code instead. That attack is called XSS, and Day 17 is about it. The rule from today: user text always goes in with textContent.

Overall Example

What this code doesWe want a notification to change three things at once when it arrives: its text, its look and its counter.
index.html
1
2
<p id="status">No new messages</p>
<span id="count">0</span>
style.css
1
2
3
4
.alert {
  color: #b3261e;
  font-weight: bold;
}
app.js
1
2
3
4
5
6
7
8
9
10
// Get the two elements
const status = document.querySelector('#status');
const count = document.querySelector('#count');

// Change the text of both
status.textContent = 'You have new messages';
count.textContent = '2';

// Switch on the alert style
status.classList.add('alert');
BROWSERindex.html
Try This

Select a paragraph, change its text to your name, and add a class that makes it bold.

What this code doesPractise: select one element and change both its text and its class.
index.html
1
<p id="name">Your name here</p>
style.css
1
.strong { font-weight: bold; }
app.js
1
2
3
4
5
6
// Get the paragraph
const name = document.querySelector('#name');

// Change its text and switch the bold class on
name.textContent = 'Joshua';
name.classList.add('strong');
BROWSERindex.html
Picture This

Picture the scoreboard operator: the board stays, and they only change the numbers and switch lights on and off. textContent is changing a number; classList is flicking a light switch.

4. Building HTML From Data

Definition

Most pages are lists drawn from data: courses, posts, products. Rendering a list means turning an array of records into HTML with map, joining the pieces into one string, and putting that string on the page in one go.

Real-Life Example

A printer at a school prints report cards: it takes the list of students and uses the same layout for every one. You design the layout once; the data fills it in.

How It Works

  1. map turns each record into a string of HTML (Day 3).
  2. join('') joins those strings into one, with nothing between them.
  3. innerHTML puts the finished HTML on the page, in one step.
What this code doesWe want to turn an array of courses into a list on the page, one list item per course, using the same layout for each.
index.html
1
<ul id="courses"></ul>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// The data: three course records
const courses = [
  { name: 'HTML & CSS', hours: 40, done: true },
  { name: 'JavaScript', hours: 60, done: false },
  { name: 'React', hours: 50, done: false }
];

// Turn a done flag into a word (Day 2 style)
function statusOf(course) {
  if (course.done) return 'completed';
  return 'in progress';
}

// Get the empty list
const list = document.querySelector('#courses');

// One <li> per course, joined into one string, placed once
list.innerHTML = courses
  .map((course) => `<li>${course.name} · ${course.hours}h · ${statusOf(course)}</li>`)
  .join('');
BROWSERindex.html
Common Mistake: Forgetting join

map gives you an array of strings. Put an array straight into innerHTML and JavaScript turns it into text by joining the items with commas.

What this code doesWe want to see what appears on the page when the array from map is used without join.
index.html
1
<ul id="courses"></ul>
app.js
1
2
3
4
5
6
// Two course names
const courses = ['HTML', 'JavaScript'];

// map WITHOUT join
document.querySelector('#courses').innerHTML = courses
  .map((course) => `<li>${course}</li>`);
BROWSERindex.html
Important Rule

Build the whole string, then assign it to innerHTML once. Assigning inside a loop makes the browser rebuild the page on every pass. With ten items nobody notices; with a thousand the page stutters, and you will measure exactly that on Day 14.

Try This

Render an array of three city names as a list.

What this code doesPractise: render a list from an array with map and join.
index.html
1
<ul id="cities"></ul>
app.js
1
2
3
4
5
6
7
// The data
const cities = ['Lagos', 'Kano', 'Enugu'];

// Render one <li> per city
document.querySelector('#cities').innerHTML = cities
  .map((city) => `<li>${city}</li>`)
  .join('');
BROWSERindex.html
Picture This

Picture the report-card printer: one template, many students. map fills the template for each student, join staples the pages together, and innerHTML hands the whole stack over at once.

5. Events: Reacting to a Person

Definition

An event is something that happens on the page: a click, a key press, a form being sent. An event listener is a function you ask the browser to run whenever that event happens.

Real-Life Example

A doorbell. You do not stand by the door all day checking. You wire the bell, and when someone presses it, it rings and you respond. addEventListener is wiring the doorbell.

Types of Events You Will Use Most

EventFires when
clickAnything is clicked or tapped
inputA field changes, on every keystroke
changeA select or checkbox changes, or a field changes and is left
submitA form is submitted, by a button or by Enter
keydownA key goes down: useful for Escape and Enter

Type 1: click

What this code doesWe want a counter that goes up each time the button is clicked. The picture shows the page after three real clicks.
index.html
1
2
<button id="add">Add one</button>
<p>Count: <span id="count">0</span></p>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
// Keep the count in a variable
let count = 0;

// Get the button and the number on the page
const button = document.querySelector('#add');
const display = document.querySelector('#count');

// When the button is clicked, add one and show the new count
button.addEventListener('click', () => {
  count = count + 1;
  display.textContent = count;
});
BROWSERindex.html · after clicking Add one three times

The Event Object

The browser hands your listener an event object describing what happened. event.target is the element the event started on.

What this code doesWe want to know which button was clicked by reading it from the event object.
index.html
1
2
<button class="size">Small</button>
<button class="size">Large</button>
app.js
1
2
3
4
5
// Listen for clicks anywhere in the document
document.addEventListener('click', (event) => {
  // event.target is the element that was clicked
  console.log('You chose:', event.target.textContent);
});
BROWSERindex.html · after clicking Large
CONSOLE   BROWSER DEVTOOLS
You chose: Large

Type 2: input

What this code doesWe want a live preview that copies each character into the page as the user types it.
index.html
1
2
<input id="name" placeholder="Type your name">
<p>Hello, <span id="preview"></span></p>
app.js
1
2
3
4
5
6
7
8
// Get the field and the preview
const field = document.querySelector('#name');
const preview = document.querySelector('#preview');

// On every keystroke, copy the field's value into the preview
field.addEventListener('input', () => {
  preview.textContent = field.value;
});
BROWSERindex.html · after typing "Ada"
Try This

Make a button that changes its own text to "Clicked!" when it is clicked.

What this code doesPractise: wire a click listener that changes the page.
index.html
1
<button id="me">Click me</button>
app.js
1
2
3
4
5
6
7
// Get the button
const button = document.querySelector('#me');

// Change its own text when clicked
button.addEventListener('click', () => {
  button.textContent = 'Clicked!';
});
BROWSERindex.html · after one click
Picture This

Picture wiring doorbells around a house. Each listener is a bell on one door, and the function is what you do when that bell rings.

6. Forms Without a Page Reload

Definition

A form's default behaviour is to send itself to a server and reload the page. event.preventDefault() stops that, so your JavaScript can check the input and respond on the same page.

Real-Life Example

A receptionist checks your form before sending it upstairs. If the email box is empty, they hand it straight back with a note, instead of sending it off and making you wait for a rejection.

How It Works

  1. Listen for submit on the form (not click on the button), so Enter works too.
  2. Call event.preventDefault() first.
  3. Read each field with .value and tidy it with .trim(), which removes spaces from the start and end.
  4. Check each rule, show a message and return early if it fails.

includes('@') asks whether a piece of text contains the @ character.

What this code doesWe want to validate an email field on the page: submit with the box empty, and see the message the user gets.
index.html
1
2
3
4
5
<form id="signup">
  <input id="email" placeholder="Email">
  <button>Sign up</button>
  <p id="error"></p>
</form>
style.css
1
#error { color: #b3261e; }
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Get the form and the error message area
const form = document.querySelector('#signup');
const error = document.querySelector('#error');

form.addEventListener('submit', (event) => {
  // Step 1: stop the page from reloading
  event.preventDefault();

  // Step 2: read the email and remove stray spaces
  const email = document.querySelector('#email').value.trim();

  // Step 3: check the rules, stopping at the first failure
  if (email === '') {
    error.textContent = 'Email is required.';
    return;
  }
  if (!email.includes('@')) {
    error.textContent = 'That does not look like an email address.';
    return;
  }

  // Step 4: everything passed
  error.textContent = '';
  console.log('Submitting', email);
});
BROWSERindex.html · after pressing Sign up with the box empty
What this code doesWe want to see the same form with a typing mistake: an address with no @ in it.
index.html
1
2
3
4
5
<form id="signup">
  <input id="email" placeholder="Email">
  <button>Sign up</button>
  <p id="error"></p>
</form>
style.css
1
#error { color: #b3261e; }
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Get the form and the error message area
const form = document.querySelector('#signup');
const error = document.querySelector('#error');

form.addEventListener('submit', (event) => {
  // Step 1: stop the page from reloading
  event.preventDefault();

  // Step 2: read the email and remove stray spaces
  const email = document.querySelector('#email').value.trim();

  // Step 3: check the rules, stopping at the first failure
  if (email === '') {
    error.textContent = 'Email is required.';
    return;
  }
  if (!email.includes('@')) {
    error.textContent = 'That does not look like an email address.';
    return;
  }

  // Step 4: everything passed
  error.textContent = '';
  console.log('Submitting', email);
});
BROWSERindex.html · after typing chidinma.gmail.com and pressing Sign up
What this code doesWe want to see the form accept a valid address and hand it on.
index.html
1
2
3
4
5
<form id="signup">
  <input id="email" placeholder="Email">
  <button>Sign up</button>
  <p id="error"></p>
</form>
style.css
1
#error { color: #b3261e; }
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Get the form and the error message area
const form = document.querySelector('#signup');
const error = document.querySelector('#error');

form.addEventListener('submit', (event) => {
  // Step 1: stop the page from reloading
  event.preventDefault();

  // Step 2: read the email and remove stray spaces
  const email = document.querySelector('#email').value.trim();

  // Step 3: check the rules, stopping at the first failure
  if (email === '') {
    error.textContent = 'Email is required.';
    return;
  }
  if (!email.includes('@')) {
    error.textContent = 'That does not look like an email address.';
    return;
  }

  // Step 4: everything passed
  error.textContent = '';
  console.log('Submitting', email);
});
BROWSERindex.html · after typing a valid address with stray spaces and pressing Sign up
CONSOLE   BROWSER DEVTOOLS
Submitting [email protected]
Three Habits for Every Form preventDefault() first. .trim() on every text value. An early return after each failed check, so the rest of the function only ever runs on good input.
Picture This

Picture the receptionist: they catch the form at the desk, check each box in order, and hand it back with one clear note the moment something is wrong.

7. Bubbling, and One Listener Instead of Fifty

Definition

When you click an element, the event fires on that element and then travels up through every parent to the top of the page. That upward journey is called bubbling. Event delegation uses it on purpose: one listener on the parent handles clicks on all of its children.

Real-Life Example

Instead of giving every student in a class their own phone line to the head teacher, the class has one line through the class teacher, who says which student is calling. New students who join later use the same line automatically.

How It Works

  1. Put one listener on the parent (for example the list).
  2. Inside it, use event.target.closest(selector) to find which child was clicked. closest walks up from where the click landed until it finds a match.
  3. If nothing matched, return and do nothing.
What this code doesWe want every Delete button in a task list to work through one listener on the list. The picture shows the list after deleting the middle task.
index.html
1
2
3
4
5
<ul id="tasks">
  <li>Read Day 4 <button class="delete">Delete</button></li>
  <li>Do the exercise <button class="delete">Delete</button></li>
  <li>Push to GitHub <button class="delete">Delete</button></li>
</ul>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// ONE listener, on the parent list
const list = document.querySelector('#tasks');

list.addEventListener('click', (event) => {
  // Which delete button (if any) did the click land on?
  const deleteButton = event.target.closest('.delete');

  // They clicked something else in the list: do nothing
  if (!deleteButton) return;

  // Remove the whole row the button sits in
  deleteButton.closest('li').remove();
});
BROWSERindex.html · after deleting the middle task
Why This Matters More Than It Looks

Give each button its own listener and you must remember to add one to every new button you create later. Forget once, and the newest row is the one that will not delete: a bug that only appears after the user adds something. One listener on the parent has no such problem.

Picture This

Picture the one phone line through the class teacher. It does not matter how many students join the class later: every call still comes through the same line.

8. Creating Elements Properly

Definition

document.createElement(tag) builds a new element in JavaScript. You set its text and classes, then attach it to the page with append. Because you set text with textContent, user text can never turn into code.

Real-Life Example

innerHTML is ordering a whole prefabricated room from a description. createElement is building the room brick by brick: slower to write, but you know exactly what went into it.

What this code doesWe want a function that builds one task row safely from any text, and use it to add a row to the list.
index.html
1
<ul id="tasks"></ul>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Build one row from a piece of text
function taskRow(text) {
  // The row itself
  const li = document.createElement('li');

  // The task text, set safely as text
  const span = document.createElement('span');
  span.textContent = text;

  // The delete button
  const button = document.createElement('button');
  button.className = 'delete';
  button.textContent = 'Delete';

  // Put the text and the button inside the row
  li.append(span, button);
  return li;
}

// Add a row to the list, including text that looks like HTML
const list = document.querySelector('#tasks');
list.append(taskRow('Finish Day 4 exercise'));
list.append(taskRow('<b>bold</b>'));
BROWSERindex.html

The second row shows the tags as plain characters. That is textContent doing its job.

Do This Now

Build one page with a text input, an Add button and an empty list. No styling needed today: behaviour only. It must do all five of these:

  1. Typing text and pressing Add appends it to the list.
  2. Submitting an empty field shows an error message and adds nothing.
  3. Every row has a Delete button, handled by one delegated listener on the list.
  4. While the list is empty, a "Nothing here yet" message shows; it hides as soon as there is a row.
  5. User text goes in with textContent, never innerHTML.
What this code doesExercise: the full task list. The picture shows it after a real session: an empty submit, three tasks added (one of them containing tags), and one deleted.
index.html
1
2
3
4
5
6
7
<form id="add-form">
  <input id="task" placeholder="New task">
  <button>Add</button>
</form>
<p id="error"></p>
<p id="empty">Nothing here yet</p>
<ul id="list"></ul>
style.css
1
#error { color: #b3261e; }
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Everything we need from the page
const form = document.querySelector('#add-form');
const input = document.querySelector('#task');
const error = document.querySelector('#error');
const empty = document.querySelector('#empty');
const list = document.querySelector('#list');

// Show the empty message only when the list has no rows
function updateEmpty() {
  empty.hidden = list.children.length > 0;
}

// Adding a task
form.addEventListener('submit', (event) => {
  event.preventDefault();
  const text = input.value.trim();

  // Reject an empty task
  if (text === '') {
    error.textContent = 'Type a task first.';
    return;
  }
  error.textContent = '';

  // Build the row safely
  const li = document.createElement('li');
  const span = document.createElement('span');
  span.textContent = text;
  const button = document.createElement('button');
  button.className = 'delete';
  button.textContent = 'Delete';
  li.append(span, button);
  list.append(li);

  // Clear the box and update the empty message
  input.value = '';
  updateEmpty();
});

// Deleting a task: one listener on the list
list.addEventListener('click', (event) => {
  const button = event.target.closest('.delete');
  if (!button) return;
  button.closest('li').remove();
  updateEmpty();
});

// Set the empty message correctly on load
updateEmpty();
BROWSERindex.html · after adding three tasks and deleting the first
Extend It Tonight

Add a counter above the list that always shows how many tasks there are. Update it in the same place you update the empty message.

Checkpoint

Q1. Your script says null for every element. What is almost certainly wrong?

The script ran before the elements existed. Move the script to the end of the body, or add defer.

Q2. list.innerHTML = items.map(...) shows commas between every row. Why?

map returns an array, and turning an array into text joins it with commas. Add .join('').

Q3. When must you use textContent rather than innerHTML?

Whenever the text came from a user. innerHTML reads what you give it as HTML, so a user can inject tags. textContent always treats it as text.

Q4. Why is one delegated listener better than one listener per button?

Rows added later still work. A listener per button only exists on buttons that were there when it was attached, so newly created rows do nothing.

Q5. Why listen for submit on the form rather than click on the button?

submit fires for the button and for pressing Enter in a field. A click listener misses the Enter key.

Tick Before You Move On

  • ☐ I can explain why a refresh undoes my changes
  • ☐ My script runs after the page exists, and no selector returns null
  • ☐ I changed text, classes and styles from JavaScript
  • ☐ I built a list from an array with map and join
  • ☐ I handled click, input and submit events
  • ☐ My form uses preventDefault, trim and early returns
  • ☐ Delete works through one delegated listener on the parent
  • ☐ I tested with bold and it displayed as text, not as markup

Quick Recap

The DOM is a live tree in memory built from your HTML, so a refresh undoes every change. querySelector finds one element, querySelectorAll finds many; both take CSS selectors. textContent for user text, innerHTML only for markup you wrote yourself. map and join turn an array into HTML; assign it once. addEventListener runs your function when something happens, and hands you an event object. preventDefault stops a form reloading the page. Events bubble up, so one listener on the parent handles rows that do not exist yet.

data-id stores a value on an element, and button.dataset.id reads it back as text, which is why it is converted with Number() before comparing.

Final Scenario

Imagine the Webbo3 dashboard shows your list of courses. You click "Mark done" on one of them, and it updates instantly without the page reloading.

That is everything from today: the courses are an array, map and join draw them, one delegated listener hears the click, and the page is redrawn from the updated data.

What this code doesWe want the full loop of a real interface: data drawn onto the page, a click that changes the data, and the page redrawn from it. The picture shows the page after marking JavaScript done.
index.html
1
<ul id="courses"></ul>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// The data
const courses = [
  { id: 1, name: 'HTML & CSS', done: true },
  { id: 2, name: 'JavaScript', done: false }
];

const list = document.querySelector('#courses');

// Draw the whole list from the data
function render() {
  list.innerHTML = courses
    .map((course) => {
      if (course.done) return `<li>${course.name}: done</li>`;
      return `<li>${course.name} <button data-id="${course.id}">Mark done</button></li>`;
    })
    .join('');
}

// One listener: update the DATA, then redraw
list.addEventListener('click', (event) => {
  const button = event.target.closest('button');
  if (!button) return;
  const course = courses.find((item) => item.id === Number(button.dataset.id));
  course.done = true;
  render();
});

render();
BROWSERindex.html · after clicking Mark done

Change the data, then redraw from the data. Hold on to that pattern: every interface you build from now on works that way.

Tomorrow, Day 5: your first solo project. You will build something real with everything from this week, and then practise the part that matters as much as the code: explaining and defending it.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.