BiTree
  • Search For Lessons
  • Curriculum
  • Pricing
  • For Educators
  • Become a Tutor
  • About
  • Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
BiTree

Live coding lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Search For Lessons
  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Become a Tutor
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 BiTree. All rights reserved.
Curriculum/Web Development/JavaScript and the DOM
50 minBeginner

JavaScript and the DOM

After this lesson, you will be able to: Make a webpage interactive by selecting elements, listening for events, and updating the page in real time using the DOM.

The DOM (Document Object Model) is the bridge between your JavaScript code and your HTML page. This lesson covers selecting elements, reading and changing them, and listening for user events like clicks and form submissions, the foundations of every interactive webpage.

Prerequisites:JavaScript Fundamentals

What is the DOM?

When the browser loads your HTML, it builds a tree of objects, one for each element. That tree is the DOM. Your JavaScript can read and change any node in the tree, and the page updates instantly. The DOM is what makes JavaScript powerful in the browser.

Diagram coming soon!

Tree diagram of the DOM: html at the top, branching to head and body, body branching to header, main, footer, each with their own children

Selecting elements

querySelector accepts any CSS selector. It's the only selector function you really need.

tsx
// Single element (first match)
const title = document.querySelector("h1");
const btn = document.querySelector("#submit-btn");
const card = document.querySelector(".card");
// Multiple elements (NodeList)
const allButtons = document.querySelectorAll("button");
allButtons.forEach((b) => console.log(b.textContent));

Reading and changing elements

Once selected, you can read and write properties.

tsx
const heading = document.querySelector("h1");
// Read
console.log(heading.textContent); // text inside
console.log(heading.classList); // list of classes
// Write
heading.textContent = "New title";
heading.style.color = "#FF4848";
heading.classList.add("highlighted");
heading.classList.toggle("hidden");

Listening for events

addEventListener attaches a function that runs when the event happens.

tsx
const btn = document.querySelector("#greet-btn");
const out = document.querySelector("#output");
btn.addEventListener("click", () => {
out.textContent = "Hello, world!";
});
// Form submit
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // stop the page from reloading
const value = form.querySelector("input[name='email']").value;
console.log("Submitted:", value);
});

ℹ️ Tip, defer your script tag

If your <script> tag runs before the HTML loads, querySelector returns null. The simplest fix: put your <script> tag at the end of <body>, or use <script defer src="..."></script>.

Try it: build a click counter

Create a tiny app that counts button clicks.

  1. 1

    Create a button with id="counter" and text "Clicks: 0"

  2. 2

    In JS, select the button and create a let count = 0

  3. 3

    Add a click event listener that increments count and updates btn.textContent

  4. 4

    Reload the page and click around to verify

  5. 5

    Bonus: add a reset button that sets count back to 0

Fetching data from an API

Buttons and forms are half the DOM story. The other half is loading data from a server and putting it on the page. The browser's `fetch` API plus `async`/`await` is the standard pattern. You'll dive into the API side properly in the next lesson; here you'll see how a fetch call wires up to a click handler so the page actually updates.

fetch + async/await + the DOM

When the user clicks the button, fetch a user from a public test API and render their name into a paragraph. Notice the try/catch for network errors and the response.ok check for HTTP errors.

html
<button id="load-btn">Load user</button>
<p id="out"></p>
<script>
const btn = document.querySelector("#load-btn");
const out = document.querySelector("#out");
btn.addEventListener("click", async () => {
out.textContent = "Loading...";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const user = await response.json();
// Use textContent (not innerHTML) when inserting untrusted data
out.textContent = `${user.name} — ${user.email}`;
} catch (error) {
out.textContent = `Failed to load: ${error.message}`;
}
});
</script>
Quick Check

Why do we call event.preventDefault() inside a form's submit handler?

Hint: think about what the browser does by default when a form is submitted.

💡 Common mistakes only experienced devs catch

Five DOM and async pitfalls. (1) Running JS before the DOM exists — `<script>` in `<head>` without `defer` makes every `querySelector` return `null`. Use `defer` or put the tag at the end of `<body>`. (2) Using `innerHTML` with untrusted input — it parses as HTML and can inject scripts. Default to `textContent`; reach for `innerHTML` only when you control the string or have explicitly sanitized it. (3) Forgetting `event.preventDefault()` on form submits — the page reloads, your fetch never finishes, and you blame the API. (4) Adding the same event listener twice — clicking once fires the handler twice. If a feature only "works once" or fires repeatedly, suspect duplicate listeners. (5) Not checking `response.ok` after a fetch — `fetch` only rejects on network failure. A 404 or 500 still resolves; you must check the status manually, otherwise you'll try to `.json()` an error page and get a parse error.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←JavaScript Fundamentals
Back to Web Development
Working With APIs: HTTP, Status Codes, Postman, and fetch→