LEARN TO CODE | JAVASCRIPT | 03 INTERACTIVE PAGES
03.1 Selecting and changing DOM elements
Connect JavaScript values to visible webpage elements, and build a list from data without treating text as HTML.
01 | FROM CONSOLE MESSAGES TO VISIBLE CONTENT
So far, console.log has let you see a program’s results as a developer. A visitor should not need Developer Tools to read a quiz result or see a club list. Your next step is to connect JavaScript to the page itself.
The browser reads HTML and builds a Document Object Model , usually shortened to DOM . It is a tree of objects representing the document: a heading, paragraph or list becomes something your code can access.
Changing the DOM can change what the browser displays. It does not rewrite your index.html source file on disk. Reloading the page builds it again from the saved source and reruns the script.
In VS Code, create index.html, script.js and, when shown, style.css in one practice folder. Save changes, open the HTML page in your browser and inspect both the page and its Console. Replace the previous example unless a task explicitly extends it.
02 | PICTURE THE DOCUMENT AS A TREE
| Relationship | Example |
|---|---|
| Document | The browser document containing the page |
| Parent element | A ul containing its list items |
| Child element | An li inside that ul |
| Siblings | Two li elements with the same parent |
<main>
<h1>Our clubs</h1>
<ul id="club-list">
<li>Robotics</li>
<li>Art</li>
</ul>
</main>
The ul is the parent of the two li elements. Each element can have properties and methods, just as the objects in the previous section did. DOM elements are browser-provided objects with additional page-related features.
document gives your script access to the current document. You will often start a selection from it.
03 | SELECT ONE ELEMENT BY A CSS SELECTOR
<p id="message">Waiting...</p>
const message = document.querySelector("#message");
if (message !== null) {
message.textContent = "Hello from JavaScript";
}
querySelector returns the first matching element. The string "#message" is a CSS selector: # means an ID. The # is part of the selector, not part of the HTML id value.
Use .notice for a class selector or p for an element-name selector. If nothing matches a valid selector, the result is null. An invalid selector can instead throw a syntax-related exception.
Check null before reading a selected element’s properties when the element might be absent. In a small complete page with fixed markup, a missing match usually points to a spelling, timing or wrong-page mistake.
04 | WHY DEFER MATTERS
If your script runs before the browser has created the element it selects, a correct-looking selector can still find nothing. Our external script uses defer so it runs after HTML parsing.
<script src="script.js" defer></script>
Put this in the document head. Save JavaScript in script.js without surrounding script tags. Do not assume a copied snippet will find an element that has not been added to that page.
document.getElementById("message") is another way to retrieve an element by ID. It takes the ID itself, without #. Keep the difference between an ID and a CSS selector clear.
TRY IT YOURSELF
The HTML has id="message", but the script queries "#messages". Why does reading .textContent fail?
Reveal guidance
The selector matches nothing, so the returned value is null. Correct the ID spelling and confirm the script runs after the document is parsed.
05 | TEXTCONTENT DISPLAYS TEXT
Assigning textContent replaces an element’s contents with text. If the text contains angle brackets, they remain characters rather than being parsed as new tags.
const message = document.querySelector("#message");
if (message !== null) {
message.textContent = "<strong>This stays text</strong>";
}
This is a useful default for names, messages and other text values. innerHTML parses markup, so passing untrusted text into it can create unwanted elements or executable behaviour. We use createElement and textContent for these lessons.
textContent is not a universal safety tool for every possible context. A link URL, for example, also needs an appropriate allowed scheme. Here we are placing plain text in text-bearing elements.
Setting a parent’s textContent also replaces its existing child content, so select the specific output element you intend to update.
06 | CHANGE CLASSES AND PROPERTIES
/* style.css */
.highlight {
border-left: 4px solid #24988f;
padding-left: 12px;
background: #eef8f6;
}
const message = document.querySelector("#message");
if (message !== null) {
message.classList.add("highlight");
}
classList.add applies a class without replacing all the element’s other classes. remove takes it away; toggle switches its presence. Keep appearance rules in CSS where possible.
Other properties represent particular states. An input’s value is its current field value. A button’s disabled property is a boolean. An element’s hidden property controls whether it is hidden under ordinary styling. Changing one does not mean changing all the others.
07 | SELECT MANY ELEMENTS
const items = document.querySelectorAll("#dom-list li");
for (const item of items) {
item.classList.add("highlight");
}
This fragment assumes the complete club list below has already been built. querySelectorAll returns a NodeList of all matches. You can iterate over it with for...of.
Unlike querySelector, it returns an empty list when nothing matches, rather than null. The returned NodeList is a snapshot of matches at that moment; adding another li later does not automatically add it to that earlier result.
Select a precise region, such as #dom-list li, so the operation does not accidentally style the page’s navigation lists too.
08 | CREATE, FILL AND APPEND
const list = document.querySelector("#dom-list");
if (list !== null) {
const item = document.createElement("li");
item.textContent = "Robotics";
list.append(item);
}
createElement makes an element object. It is not visible just because it has been created; append attaches it to the list in the document.
Creating a fresh li inside a loop gives each record its own element. Appending the very same node repeatedly moves that node rather than making independent copies.
replaceChildren() with no arguments removes a container’s children. It is useful before rebuilding a list so repeated rendering does not accumulate duplicates. item.remove() removes one existing node.
09 | THE COMPLETE CLUB-LIST PROGRAM
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A data-driven club list</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<main>
<h1>A data-driven club list</h1>
<h2 id="dom-title">Clubs will appear here</h2>
<p id="dom-status" role="status" aria-live="polite">Waiting for the script.</p>
<ul id="dom-list"></ul>
<p id="dom-help">Refresh the page to rebuild this list from the source data.</p>
</main>
</body>
</html>
script.js
const clubs = ["Robotics", "Art", "Coding"];
const title = document.querySelector("#dom-title");
const status = document.querySelector("#dom-status");
const list = document.querySelector("#dom-list");
if (title !== null && status !== null && list !== null) {
title.textContent = "Explore our clubs";
title.classList.add("highlight");
list.replaceChildren();
for (const clubName of clubs) {
const item = document.createElement("li");
item.textContent = clubName;
list.append(item);
}
status.textContent = `${clubs.length} clubs available to explore.`;
} else {
console.error("Check the required club-list element IDs.");
}
The list is empty in the source HTML. The array is the data; the loop is the rendering process; the list elements are the visible output. Each role can be understood separately.
The shared download includes style.css for these project examples. Its .highlight rule appears above. The site lesson itself continues to inherit the site’s existing stylesheet.
10 | SEE THE RESULT
Clubs will appear here
Waiting for the script.
Refresh the page to rebuild this list from the source data.
This list is built by the same program shown above. In your own VS Code copy, add "Chess" to the array, save and reload. The list and count should both change.
TRY IT YOURSELF
Add a club name containing the literal characters <b>Chess</b> . How should it appear?
Reveal guidance
The characters <b>Chess</b> should be visible as text, not turn into a bold child element. textContent preserves that distinction.
11 | CHECK BEFORE MOVING ON
Test the normal three-club array, an empty array and one additional club. The empty array should produce no list items and a count of zero. Inspect the browser’s Elements panel to see the generated li nodes.
If the page does not update, confirm the selected IDs, script path, saved file and Console errors. Next, keep the same selection-and-update pattern but trigger it in response to a user event.
KEEP LEARNING
Previous page 03.2 Events and event listeners
Download Section 03 example programs
Further reading: The DOM · Event listeners · Form validation · Browser storage