JavaScript HTML DOM: Introduction to the DOM · DOM Methods · Finding Elements · Changing HTML · Changing CSS · Form Validation · Animations · The Document Object · Element Reference
Table of Contents
- What is the HTML DOM?
- DOM Methods — Your First Tools
- Finding HTML Elements
- Changing HTML Content
- Changing CSS Styles
- Form Validation with the DOM
- DOM Animations
- The Document Object in Depth
- HTML Element Reference
- Applied Exercises
- Project Simulation: Interactive Student Dashboard
- Completion Checklist & Summary
PHASE 1 — CONCEPTUAL UNDERSTANDING
1. What is the HTML DOM?
The Big Picture
When a browser loads an HTML page, it does not just display it — it reads every tag, builds a structured map of the entire page in memory, and hands that map to JavaScript. That map is called the DOM: the Document Object Model.
Think of a webpage like a family tree:
document
└── <html>
├── <head>
│ └── <title>My Page</title>
└── <body>
├── <h1>Hello World</h1>
└── <p id="intro">Welcome!</p>
Every tag becomes a node in that tree. JavaScript can walk up and down the tree, read any node, change it, add new ones, or delete them — all without reloading the page.
Real-world analogy: A DOM is like a company org chart. The CEO is
<html>, departments are<head>and<body>, and individual employees are your tags like<p>,<h1>,<button>. JavaScript is like a manager who can rename employees, add new ones, or fire them — instantly, without rebuilding the company.
Why Does the DOM Exist?
Before the DOM, if you wanted to update a webpage you had to reload the entire page from the server. The DOM was invented so that:
- Pages could react to users (clicks, typing, hovering)
- Content could update in real time (live scores, chat messages)
- Forms could validate input before submitting
- Elements could animate without Flash or plugins
Today, every interactive website you use — from Google Docs to YouTube — is built on DOM manipulation.
The DOM is a Standard
The DOM is not invented by a browser. It is a W3C standard — a set of rules every browser must follow. This means the same JavaScript DOM code works in Chrome, Firefox, Safari, and Edge.
The standard defines:
| Standard | What it covers |
|---|---|
| Core DOM | All document types (XML, HTML) |
| XML DOM | XML-specific features |
| HTML DOM | HTML-specific features — this is what we learn |
The DOM Standard Defines:
- HTML elements as objects — each tag in your HTML becomes a JavaScript object
- Properties — the data inside that object (e.g.,
innerHTML,id,style) - Methods — the actions you can call on it (e.g.,
getElementById(),appendChild()) - Events — triggers that fire when something happens (e.g.,
onclick,onchange)
Micro-Demo 1 — The DOM in One Line
<p id="demo">I am original text.</p>
<script>
document.getElementById("demo").innerHTML = "DOM changed me!";
</script>
What happens:
- Browser builds the DOM from the HTML
documentis the entry point to the entire tree.getElementById("demo")finds the<p>node withid="demo".innerHTML = "DOM changed me!"replaces the text inside it
Expected output in browser: The paragraph now reads: DOM changed me!
💡 Thinking question: What would happen if you used an
idthat doesn’t exist in the page? Trydocument.getElementById("ghost")— what would JavaScript return?
Answer: It returnsnull. Trying to set.innerHTMLonnullwill throw an error. Always check that your element exists!
2. DOM Methods — Your First Tools
What is a Method?
A method is an action you can perform. Just like a TV remote has buttons (change channel, adjust volume), DOM objects have methods (find element, change content, add child).
The two most foundational DOM methods are:
| Method | What it does |
|---|---|
document.getElementById(id) |
Finds one element by its id attribute |
document.createElement(tag) |
Creates a brand new HTML element |
getElementById — Your Most-Used Tool
<!DOCTYPE html>
<html>
<body>
<p id="greet">Good morning!</p>
<button onclick="changeGreeting()">Change Greeting</button>
<script>
function changeGreeting() {
// Step 1: Find the element
var element = document.getElementById("greet");
// Step 2: Change its content
element.innerHTML = "Good evening!";
}
</script>
</body>
</html>
Line-by-line breakdown:
| Line | Meaning |
|---|---|
document.getElementById("greet") |
Searches the entire DOM for a tag with id="greet" and returns it as a JavaScript object |
element.innerHTML |
A property of the element object — it holds the HTML content inside the tags |
= "Good evening!" |
Assigns a new value, replacing the old content |
Expected output: After clicking the button, the paragraph changes from “Good morning!” to “Good evening!” without any page reload.
innerHTML vs innerText — An Important Difference
These two properties look similar but behave differently:
<div id="box">Hello</div>
<script>
var el = document.getElementById("box");
// innerHTML treats the value as HTML markup
el.innerHTML = "<strong>Bold text</strong>";
// → Displays bold text in the browser
// innerText treats the value as plain text
el.innerText = "<strong>Bold text</strong>";
// → Displays the literal string: <strong>Bold text</strong>
</script>
⚠️ Common beginner mistake: Using
innerHTMLto insert user-typed content directly. If a user types<script>alert("hacked")</script>, it will execute! For user input, always useinnerTextor sanitize the input first.
The document Object — Your Gateway
document is a special built-in JavaScript object that represents the entire loaded page. Every DOM method starts from document because it is the root of the tree.
window
└── document ← your starting point
├── head
└── body
├── h1
└── p
You do not create document — the browser creates it automatically when the page loads.
Defining HTML Elements — The id Attribute
getElementById works because HTML allows every tag to carry a unique identifier:
<p id="score">0</p>
<p id="status">Not started</p>
The id must be unique per page — just like a national ID number. If two elements share the same id, getElementById will only find the first one, and your code will behave unpredictably.
3. Finding HTML Elements
Six Ways to Find Elements
Once you have the document, you need to locate the specific elements you want to work with. JavaScript gives you six main strategies:
| Method | Returns | Use when… |
|---|---|---|
getElementById(id) |
Single element | You know the exact id |
getElementsByTagName(tag) |
HTMLCollection | You want all <p>, all <li>, etc. |
getElementsByClassName(cls) |
HTMLCollection | You want all elements sharing a CSS class |
querySelector(selector) |
Single element | You want the first match using CSS selector syntax |
querySelectorAll(selector) |
NodeList | You want all matches using CSS selector syntax |
getElementsByName(name) |
NodeList | Used with form inputs sharing a name attribute |
Method 1: getElementById
<h2 id="title">JavaScript DOM</h2>
<script>
var title = document.getElementById("title");
console.log(title.innerHTML); // Output: JavaScript DOM
</script>
- Returns one element (or
nullif not found) - Fastest method — browser optimises id lookups
Method 2: getElementsByTagName
Returns an HTMLCollection — like an array of all matching elements.
<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>
<script>
var paragraphs = document.getElementsByTagName("p");
console.log(paragraphs.length); // Output: 3
console.log(paragraphs[0].innerHTML); // Output: First paragraph
console.log(paragraphs[2].innerHTML); // Output: Third paragraph
</script>
🔍 What is an HTMLCollection? It is a live list of DOM elements. “Live” means: if you add a new
<p>to the page later,paragraphsautomatically includes it — you don’t need to call the method again.
Looping Through a Collection
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
<script>
var items = document.getElementsByTagName("li");
for (var i = 0; i < items.length; i++) {
items[i].style.color = "green"; // Makes each item text green
}
</script>
Expected output: All three list items turn green in the browser.
Method 3: getElementsByClassName
<p class="highlight">This is highlighted.</p>
<p>This is not.</p>
<p class="highlight">This is also highlighted.</p>
<script>
var highlighted = document.getElementsByClassName("highlight");
console.log(highlighted.length); // Output: 2
</script>
💡 Multiple classes:
document.getElementsByClassName("box active")finds elements that have both classes.
Method 4: querySelector — CSS Selector Power
querySelector accepts any CSS selector string — giving you tremendous flexibility:
<div class="card">
<p id="first">Hello</p>
<p>World</p>
</div>
<script>
// By id (like getElementById)
var el1 = document.querySelector("#first");
console.log(el1.innerHTML); // Output: Hello
// By class
var el2 = document.querySelector(".card");
// By tag
var el3 = document.querySelector("p");
// By attribute
var el4 = document.querySelector("input[type='text']");
// By nesting — the first <p> inside .card
var el5 = document.querySelector(".card p");
console.log(el5.innerHTML); // Output: Hello
</script>
Method 5: querySelectorAll
Like querySelector but returns all matches as a NodeList.
<p class="item">One</p>
<p class="item">Two</p>
<p class="item">Three</p>
<script>
var items = document.querySelectorAll(".item");
items.forEach(function(item) {
item.style.fontWeight = "bold";
});
</script>
Expected output: All three paragraphs become bold.
🔑 Difference from HTMLCollection: NodeList returned by
querySelectorAllis static — it does not update if you add new matching elements later.
Searching Within an Element (Not Just the Document)
You can call these methods on any element, not just document. This lets you narrow your search:
<div id="section-a">
<p>Paragraph in A</p>
</div>
<div id="section-b">
<p>Paragraph in B</p>
</div>
<script>
var sectionA = document.getElementById("section-a");
var pInA = sectionA.getElementsByTagName("p");
console.log(pInA[0].innerHTML); // Output: Paragraph in A
// Does NOT find the paragraph in section-b
</script>
Navigating the DOM Tree with Relationships
Every element knows about its relatives:
| Property | What it returns |
|---|---|
element.parentNode |
The direct parent element |
element.childNodes |
All child nodes (including text nodes) |
element.children |
Only child elements (no text nodes) |
element.firstChild |
First child node |
element.lastChild |
Last child node |
element.nextSibling |
Next node at the same level |
element.previousSibling |
Previous node at the same level |
element.firstElementChild |
First child that is an element |
element.lastElementChild |
Last child that is an element |
<ul id="menu">
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
<script>
var menu = document.getElementById("menu");
var first = menu.firstElementChild;
var last = menu.lastElementChild;
console.log(first.innerHTML); // Output: Home
console.log(last.innerHTML); // Output: Contact
console.log(menu.children.length); // Output: 3
</script>
4. Changing HTML Content
What Can You Change?
Once you have found an element, you can change almost anything about it:
| What to change | Property / Method |
|---|---|
| Text/HTML inside a tag | .innerHTML or .innerText |
| An attribute value (src, href, alt…) | .setAttribute() or direct property |
| The entire element | .outerHTML |
| Append new child elements | .appendChild(), .insertBefore() |
| Remove elements | .removeChild(), .remove() |
| Write directly to the page (during load) | document.write() |
Changing innerHTML
<h1 id="heading">Old Title</h1>
<script>
document.getElementById("heading").innerHTML = "New Title";
</script>
Expected output: The page displays “New Title”.
Changing an Attribute
HTML attributes (like src, href, value, alt) can be changed by:
Method A — Direct property access:
<img id="photo" src="cat.jpg" alt="A cat">
<script>
var img = document.getElementById("photo");
img.src = "dog.jpg"; // Changes the image source
img.alt = "A dog"; // Changes the alt text
</script>
Method B — Using setAttribute:
<a id="link" href="https://google.com">Visit</a>
<script>
document.getElementById("link").setAttribute("href", "https://bing.com");
</script>
Read an attribute with getAttribute:
<script>
var href = document.getElementById("link").getAttribute("href");
console.log(href); // Output: https://bing.com
</script>
document.write() — Use With Caution
document.write() adds content directly into the HTML stream as the page loads:
<script>
document.write("<p>Written during page load</p>");
</script>
⚠️ Critical warning: If you call
document.write()after the page has finished loading, it overwrites the entire page. This is almost never what you want. UseinnerHTMLinstead for post-load changes.
Creating and Inserting New Elements
This is one of the most powerful DOM skills: building elements from scratch and inserting them into the page.
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button onclick="addItem()">Add Item</button>
<script>
function addItem() {
// Step 1: Create the new element
var newItem = document.createElement("li");
// Step 2: Give it content
newItem.innerHTML = "Item 3";
// Step 3: Find where to put it
var list = document.getElementById("list");
// Step 4: Append it as the last child
list.appendChild(newItem);
}
</script>
Expected output: Each button click adds a new <li>Item 3</li> to the list.
Inserting Before a Specific Element
<ul id="list">
<li id="second">Second</li>
</ul>
<script>
var newItem = document.createElement("li");
newItem.innerHTML = "First (inserted)";
var list = document.getElementById("list");
var secondItem = document.getElementById("second");
// insertBefore(newNode, referenceNode)
list.insertBefore(newItem, secondItem);
</script>
Expected output: The list now shows “First (inserted)” above “Second”.
Removing Elements
Modern approach (on the element itself):
<p id="remove-me">Delete me!</p>
<script>
document.getElementById("remove-me").remove();
</script>
Classic approach (via parent):
<script>
var child = document.getElementById("remove-me");
child.parentNode.removeChild(child);
</script>
Both achieve the same result. The .remove() approach is shorter but slightly less supported in very old browsers.
Replacing an Element
<p id="old">Old paragraph</p>
<script>
var newPara = document.createElement("p");
newPara.innerHTML = "New paragraph";
var old = document.getElementById("old");
old.parentNode.replaceChild(newPara, old);
</script>
outerHTML — Replace Including the Tag Itself
<p id="box">Hello</p>
<script>
// innerHTML changes only the CONTENT inside the tag
// outerHTML changes the ENTIRE element including the tag itself
document.getElementById("box").outerHTML = "<h2>I replaced the paragraph!</h2>";
</script>
Expected output: The <p> tag is completely replaced by an <h2>.
💡 Thinking question: After the code above runs,
document.getElementById("box")will returnnull. Why?
Answer: The element withid="box"no longer exists — it was replaced. The new<h2>doesn’t have that id.
5. Changing CSS Styles
The style Property
Every DOM element has a .style property that maps directly to its inline CSS. You can read and write any CSS property through it.
<p id="message">Style me!</p>
<script>
var el = document.getElementById("message");
el.style.color = "red";
el.style.fontSize = "24px";
el.style.fontWeight = "bold";
el.style.backgroundColor = "yellow";
el.style.padding = "10px";
</script>
Expected output: The paragraph appears large, bold, red text on a yellow background with padding.
CSS Property Name Conversion Rule
CSS uses hyphens: background-color, font-size, border-radius.
JavaScript uses camelCase: backgroundColor, fontSize, borderRadius.
The rule: remove the hyphen and capitalise the next letter.
| CSS Property | JavaScript Property |
|---|---|
background-color |
backgroundColor |
font-size |
fontSize |
border-top-width |
borderTopWidth |
z-index |
zIndex |
flex-direction |
flexDirection |
text-decoration |
textDecoration |
Toggle Visibility
A classic pattern: show or hide an element based on its current state.
<div id="panel" style="display:block;">
<p>I am visible!</p>
</div>
<button onclick="togglePanel()">Toggle</button>
<script>
function togglePanel() {
var panel = document.getElementById("panel");
if (panel.style.display === "none") {
panel.style.display = "block";
} else {
panel.style.display = "none";
}
}
</script>
Expected output: Each button click hides/shows the div.
Using CSS Classes Instead of Inline Styles
Inline styles are powerful but mix logic with presentation. A cleaner approach is to add and remove CSS classes using JavaScript.
<style>
.highlighted {
background-color: yellow;
font-weight: bold;
border: 2px solid orange;
}
</style>
<p id="note">Click a button to style me.</p>
<button onclick="addStyle()">Add Style</button>
<button onclick="removeStyle()">Remove Style</button>
<button onclick="toggleStyle()">Toggle Style</button>
<script>
var note = document.getElementById("note");
function addStyle() {
note.classList.add("highlighted");
}
function removeStyle() {
note.classList.remove("highlighted");
}
function toggleStyle() {
note.classList.toggle("highlighted");
}
</script>
The classList API — Full Reference
| Method / Property | What it does |
|---|---|
classList.add("cls") |
Adds a class |
classList.remove("cls") |
Removes a class |
classList.toggle("cls") |
Adds if missing, removes if present |
classList.contains("cls") |
Returns true if the class is present |
classList.replace("old", "new") |
Swaps one class for another |
classList.length |
Number of classes on the element |
<div id="box" class="card active large"></div>
<script>
var box = document.getElementById("box");
console.log(box.classList.contains("active")); // true
console.log(box.classList.length); // 3
box.classList.remove("large");
console.log(box.classList.length); // 2
box.classList.replace("active", "disabled");
// Now: class="card disabled"
</script>
Reading Computed Styles
.style only reads inline styles. If a style comes from a CSS file or <style> tag, you need getComputedStyle:
<style>
#text { color: blue; font-size: 18px; }
</style>
<p id="text">Blue text</p>
<script>
var el = document.getElementById("text");
// This reads the COMPUTED (final applied) style
var computed = window.getComputedStyle(el);
console.log(computed.color); // Output: rgb(0, 0, 255)
console.log(computed.fontSize); // Output: 18px
</script>
6. Form Validation with the DOM
Why Validate on the Client Side?
When a user fills out a form and clicks Submit, the data travels to a server. Sending invalid data (empty fields, wrong email format) wastes bandwidth and frustrates users with slow error feedback. Client-side validation catches errors instantly, before the data ever leaves the browser.
⚠️ Important: Client-side validation is for user experience. Always also validate on the server side, because users can bypass JavaScript.
A Basic Required-Field Check
<form name="myForm" onsubmit="return validateForm()">
<label>Name:</label>
<input type="text" id="name" placeholder="Enter your name">
<br>
<button type="submit">Submit</button>
</form>
<script>
function validateForm() {
var nameInput = document.getElementById("name").value;
// .trim() removes leading/trailing spaces
if (nameInput.trim() === "") {
alert("Name is required!");
return false; // Stops form submission
}
return true; // Allows form submission
}
</script>
Key concept: return false from an onsubmit handler prevents the form from submitting. return true allows it.
Validating an Email Address
<input type="text" id="email" placeholder="Enter email">
<span id="emailError" style="color:red;"></span>
<script>
function validateEmail() {
var email = document.getElementById("email").value;
var errorSpan = document.getElementById("emailError");
// A simple email pattern check
var pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(email)) {
errorSpan.innerText = "Please enter a valid email.";
return false;
} else {
errorSpan.innerText = "";
return true;
}
}
</script>
Numeric Range Validation
<label>Age (18–65):</label>
<input type="number" id="age">
<p id="ageError" style="color:red;"></p>
<script>
function validateAge() {
var age = Number(document.getElementById("age").value);
var error = document.getElementById("ageError");
if (isNaN(age) || age < 18 || age > 65) {
error.innerText = "Age must be between 18 and 65.";
return false;
}
error.innerText = "";
return true;
}
</script>
Automatic HTML5 Validation vs JavaScript Validation
Modern HTML5 includes built-in validation attributes:
| Attribute | Behaviour |
|---|---|
required |
Field must not be empty |
type="email" |
Must match email format |
type="number" |
Must be a number |
min / max |
Number must be in range |
minlength / maxlength |
Text length limits |
pattern |
Must match a regex pattern |
<input type="email" required placeholder="your@email.com">
<input type="number" min="18" max="65" required>
HTML5 validation fires automatically on submit. JavaScript validation gives you more control over the error messages and timing.
Real-Time Validation with Events
Instead of waiting for form submission, validate as the user types:
<input type="text" id="username" oninput="checkUsername(this.value)">
<small id="usernameMsg"></small>
<script>
function checkUsername(value) {
var msg = document.getElementById("usernameMsg");
if (value.length < 3) {
msg.style.color = "red";
msg.innerText = "Too short (minimum 3 characters)";
} else if (value.length > 20) {
msg.style.color = "red";
msg.innerText = "Too long (maximum 20 characters)";
} else {
msg.style.color = "green";
msg.innerText = "✓ Username looks good!";
}
}
</script>
Expected output: As the user types, the message updates instantly with colour-coded feedback.
Password Confirmation Check
<input type="password" id="pass1" placeholder="Password">
<input type="password" id="pass2" placeholder="Confirm Password">
<p id="matchMsg"></p>
<script>
function checkPasswords() {
var p1 = document.getElementById("pass1").value;
var p2 = document.getElementById("pass2").value;
var msg = document.getElementById("matchMsg");
if (p2 === "") {
msg.innerText = "";
} else if (p1 === p2) {
msg.style.color = "green";
msg.innerText = "✓ Passwords match";
} else {
msg.style.color = "red";
msg.innerText = "✗ Passwords do not match";
}
}
document.getElementById("pass2").addEventListener("input", checkPasswords);
</script>
7. DOM Animations
How DOM Animations Work (The Concept)
CSS transitions and animations are great for simple effects. But JavaScript DOM animations give you precise control using:
setInterval(fn, ms)— runs a function repeatedly everymsmillisecondsclearInterval(id)— stops a running intervalrequestAnimationFrame(fn)— the modern, performance-friendly alternative
The core idea: change a CSS property by a small amount, many times per second, to create the illusion of smooth movement.
The Timer Functions Explained
| Function | What it does |
|---|---|
setTimeout(fn, ms) |
Calls fn once after ms milliseconds |
setInterval(fn, ms) |
Calls fn repeatedly every ms milliseconds |
clearInterval(id) |
Stops an interval using its returned id |
requestAnimationFrame(fn) |
Calls fn before the next screen repaint (~60fps) |
Micro-Demo: Blinking Text
<p id="blink">Notice me!</p>
<script>
var visible = true;
setInterval(function() {
var el = document.getElementById("blink");
el.style.visibility = visible ? "hidden" : "visible";
visible = !visible;
}, 500); // Every 500ms (half a second)
</script>
Expected output: The text alternates between visible and hidden twice per second.
Building a Slide Animation Step by Step
This is the classic W3Schools sliding box animation — let’s build it together and understand every line.
HTML structure:
<div id="container" style="
width: 400px;
height: 400px;
position: relative;
background: #ddd;
">
<div id="box" style="
width: 50px;
height: 50px;
background: red;
position: absolute;
top: 0px;
left: 0px;
"></div>
</div>
<button onclick="startMove()">Start</button>
<button onclick="stopMove()">Stop</button>
Why position: relative on the container?
The box uses position: absolute which positions it relative to its nearest positioned ancestor. By setting relative on the container, the box moves within the container, not the entire page.
JavaScript:
var timer = null; // Stores the interval ID so we can stop it later
function startMove() {
// Prevent starting multiple intervals at once
clearInterval(timer);
timer = setInterval(function() {
var box = document.getElementById("box");
// Read current left position (removing "px" to get a number)
var currentLeft = parseInt(box.style.left);
// If box has reached the right edge of the container, stop
if (currentLeft >= 350) { // 400px container - 50px box = 350px max
clearInterval(timer);
} else {
// Move box 1px to the right
box.style.left = (currentLeft + 1) + "px";
}
}, 5); // Every 5ms ≈ 200 frames per second
}
function stopMove() {
clearInterval(timer);
}
Line-by-line breakdown:
| Code | Meaning |
|---|---|
var timer = null |
Declares a variable outside the function so it persists between calls |
clearInterval(timer) |
Ensures no duplicate intervals run at the same time |
parseInt(box.style.left) |
Converts "0px" (a string) into 0 (a number) for arithmetic |
(currentLeft + 1) + "px" |
Increases by 1, then converts back to "1px" string |
clearInterval(timer) at the boundary |
Stops the animation when the box reaches the edge |
Using requestAnimationFrame — The Modern Way
setInterval has a problem: it fires on its own schedule, not in sync with the browser’s painting cycle. This can cause jank (stuttering). requestAnimationFrame is smarter:
var pos = 0;
var animating = false;
var animId;
function animate() {
if (!animating) return;
var box = document.getElementById("box");
pos++;
if (pos >= 350) {
pos = 350;
animating = false;
}
box.style.left = pos + "px";
// Request the NEXT frame
animId = requestAnimationFrame(animate);
}
function startMove() {
if (!animating) {
animating = true;
animate();
}
}
function stopMove() {
animating = false;
cancelAnimationFrame(animId);
}
Why is this better?
- Syncs with the display refresh rate (usually 60fps)
- Pauses automatically when the tab is not visible (saves CPU/battery)
- Smoother than
setInterval
Animating Multiple Properties
<div id="bubble" style="
width: 60px;
height: 60px;
border-radius: 50%;
background: blue;
position: absolute;
top: 200px;
left: 0px;
opacity: 1;
"></div>
<script>
var x = 0;
var opacity = 1;
function animate() {
var el = document.getElementById("bubble");
x += 2;
opacity -= 0.005;
el.style.left = x + "px";
el.style.opacity = opacity;
if (x < 400 && opacity > 0) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
</script>
Expected output: A blue circle slides to the right while gradually fading out.
8. The Document Object in Depth
What Is the document Object?
The document object is the root of the HTML DOM — it represents the loaded HTML page and provides access to every element, attribute, and text node in it.
You never create document — it’s automatically available in any JavaScript running in a browser.
Key document Properties
| Property | What it returns |
|---|---|
document.title |
The page title (from <title> tag) |
document.URL |
Full URL of the current page |
document.domain |
Domain name (e.g., "example.com") |
document.referrer |
URL of the page that linked here |
document.lastModified |
Date/time the page was last modified |
document.readyState |
Loading state: "loading", "interactive", "complete" |
document.characterSet |
Character encoding (usually "UTF-8") |
document.doctype |
The DOCTYPE declaration |
document.head |
The <head> element |
document.body |
The <body> element |
document.documentElement |
The <html> element |
document.forms |
HTMLCollection of all <form> elements |
document.images |
HTMLCollection of all <img> elements |
document.links |
HTMLCollection of all <a> elements with href |
document.scripts |
HTMLCollection of all <script> elements |
document.styleSheets |
List of all linked/embedded stylesheets |
document.cookie |
Get or set cookies |
Practical Examples
Reading page information:
console.log(document.title); // "My Page Title"
console.log(document.URL); // "https://example.com/page.html"
console.log(document.domain); // "example.com"
console.log(document.lastModified); // "03/17/2026 10:30:00"
console.log(document.readyState); // "complete"
Accessing the <body> directly:
document.body.style.backgroundColor = "lightblue";
document.body.style.fontFamily = "Arial, sans-serif";
Working with forms:
console.log(document.forms.length); // Number of forms on the page
console.log(document.forms[0].id); // id of first form
console.log(document.forms["loginForm"]); // Access by name attribute
Working with images:
var images = document.images;
for (var i = 0; i < images.length; i++) {
console.log(images[i].src); // Prints URL of each image
}
Working with links:
var links = document.links;
for (var i = 0; i < links.length; i++) {
links[i].style.color = "purple"; // Changes all link colours
}
Creating Elements — Full Node API
The document object is responsible for creating all new nodes:
| Method | Creates |
|---|---|
document.createElement(tag) |
A new HTML element |
document.createTextNode(text) |
A raw text node |
document.createAttribute(name) |
A new attribute node |
document.createComment(text) |
An HTML comment node |
document.createDocumentFragment() |
A lightweight container for building DOM structures off-screen |
Text node example:
var para = document.createElement("p");
var text = document.createTextNode("Hello from a text node!");
para.appendChild(text);
document.body.appendChild(para);
// Result: <p>Hello from a text node!</p> added to the page
DocumentFragment — Performance Trick
When adding many elements, inserting them one by one causes the browser to repaint the screen repeatedly. A DocumentFragment lets you build the structure in memory first, then insert everything at once:
var fragment = document.createDocumentFragment();
for (var i = 1; i <= 100; i++) {
var li = document.createElement("li");
li.innerText = "Item " + i;
fragment.appendChild(li); // No repaint yet
}
// ONE repaint only
document.getElementById("myList").appendChild(fragment);
This is significantly faster than 100 individual appendChild calls on the live DOM.
Cookies with document.cookie
// Set a cookie
document.cookie = "username=Alice; expires=Thu, 31 Dec 2026 12:00:00 UTC; path=/";
// Read all cookies (returns one big string)
console.log(document.cookie);
// Output: "username=Alice; theme=dark"
// To read a specific cookie, you need to parse the string manually
function getCookie(name) {
var cookies = document.cookie.split("; ");
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split("=");
if (parts[0] === name) return parts[1];
}
return null;
}
console.log(getCookie("username")); // Output: Alice
💡 For modern projects, use
localStorageorsessionStoragefor client-side data storage. Cookies are mainly for data that needs to be sent to the server.
document.open(), document.write(), document.close()
These three work together to write an entirely new document:
document.open();
document.write("<html><body><h1>New document written by JS!</h1></body></html>");
document.close();
⚠️ This completely replaces the existing page. Used mainly in old-style popup windows. Avoid in modern projects.
9. HTML Element Reference
The Element as an Object
Every HTML element in the DOM is represented as a JavaScript object. That object has:
- Properties — data about the element (its id, class, style, content, etc.)
- Methods — actions you can perform (click it, focus it, blur it, etc.)
This section catalogues the most important ones.
Universal Element Properties (Available on Every Element)
| Property | Type | Description |
|---|---|---|
id |
String | The element’s id attribute |
tagName |
String | Tag name in UPPERCASE ("DIV", "P", "INPUT") |
nodeName |
String | Same as tagName for elements |
nodeType |
Number | 1=Element, 3=Text, 8=Comment, 9=Document |
innerHTML |
String | HTML content inside the element |
innerText |
String | Visible text content (no tags) |
textContent |
String | All text content (includes hidden text) |
outerHTML |
String | The element including its own opening/closing tags |
className |
String | All CSS classes as a space-separated string |
classList |
DOMTokenList | Live list of classes (use .add(), .remove(), .toggle()) |
style |
CSSStyleDeclaration | Inline styles |
attributes |
NamedNodeMap | All attributes of the element |
dataset |
DOMStringMap | Custom data-* attributes |
hidden |
Boolean | Whether the element is hidden |
tabIndex |
Number | Keyboard tab order |
title |
String | Tooltip text |
lang |
String | Language code |
dir |
String | Text direction: "ltr" or "rtl" |
accessKey |
String | Keyboard shortcut key |
contentEditable |
String | "true" if user can edit the element’s content |
isContentEditable |
Boolean | Read-only. Is the element editable? |
draggable |
Boolean | Can the element be dragged? |
Position and Size Properties
| Property | Description |
|---|---|
offsetWidth |
Total width including padding and border |
offsetHeight |
Total height including padding and border |
offsetLeft |
Distance from left edge of offset parent |
offsetTop |
Distance from top edge of offset parent |
offsetParent |
Nearest positioned ancestor element |
clientWidth |
Width including padding, excluding border and scrollbar |
clientHeight |
Height including padding, excluding border |
scrollWidth |
Full scrollable width |
scrollHeight |
Full scrollable height |
scrollLeft |
Pixels scrolled horizontally |
scrollTop |
Pixels scrolled vertically |
getBoundingClientRect() |
Returns size and position relative to viewport |
var box = document.getElementById("box");
console.log(box.offsetWidth); // e.g. 300 (px)
console.log(box.offsetTop); // e.g. 120 (px from top of offset parent)
var rect = box.getBoundingClientRect();
console.log(rect.top); // Distance from top of viewport
console.log(rect.left); // Distance from left of viewport
console.log(rect.width); // Element width
console.log(rect.height); // Element height
Tree Navigation Properties
| Property | Returns |
|---|---|
parentNode |
Parent node (may be a text node or document) |
parentElement |
Parent element (always an element or null) |
childNodes |
NodeList of all child nodes |
children |
HTMLCollection of child elements only |
firstChild |
First child node |
lastChild |
Last child node |
firstElementChild |
First child element |
lastElementChild |
Last child element |
nextSibling |
Next sibling node |
previousSibling |
Previous sibling node |
nextElementSibling |
Next sibling element |
previousElementSibling |
Previous sibling element |
childElementCount |
Number of child elements |
Universal Element Methods
| Method | Description |
|---|---|
getAttribute(name) |
Get the value of an attribute |
setAttribute(name, value) |
Set an attribute value |
removeAttribute(name) |
Remove an attribute |
hasAttribute(name) |
Returns boolean: does the attribute exist? |
appendChild(node) |
Add a child node at the end |
insertBefore(new, ref) |
Insert before a reference child |
removeChild(child) |
Remove a child node |
replaceChild(new, old) |
Replace a child node |
cloneNode(deep) |
Clone the element; deep=true clones children too |
contains(node) |
Returns boolean: is the node a descendant? |
matches(selector) |
Returns boolean: does the element match a CSS selector? |
closest(selector) |
Walks up the tree, returns first ancestor matching selector |
querySelector(sel) |
Finds first matching descendant |
querySelectorAll(sel) |
Finds all matching descendants |
insertAdjacentHTML(pos, html) |
Inserts HTML at a specific position |
insertAdjacentElement(pos, el) |
Inserts an element at a specific position |
scrollIntoView() |
Scrolls the element into the visible viewport |
focus() |
Gives focus to the element |
blur() |
Removes focus |
click() |
Programmatically clicks the element |
remove() |
Removes the element from the DOM |
insertAdjacentHTML Positions
<div id="target">TARGET</div>
<script>
var el = document.getElementById("target");
el.insertAdjacentHTML("beforebegin", "<p>Before the div</p>");
// <p>Before the div</p>
// <div id="target">TARGET</div>
el.insertAdjacentHTML("afterbegin", "<p>First inside div</p>");
// <div id="target"><p>First inside div</p>TARGET</div>
el.insertAdjacentHTML("beforeend", "<p>Last inside div</p>");
// <div id="target">TARGET<p>Last inside div</p></div>
el.insertAdjacentHTML("afterend", "<p>After the div</p>");
// <div id="target">TARGET</div>
// <p>After the div</p>
</script>
cloneNode — Copying Elements
<div id="original">
<p>Hello</p>
<span>World</span>
</div>
<script>
var original = document.getElementById("original");
// Shallow clone — copies only the div, NOT its children
var shallow = original.cloneNode(false);
// Deep clone — copies the div AND all its children
var deep = original.cloneNode(true);
document.body.appendChild(deep);
// Adds an identical copy of the div (with <p> and <span>) to the body
</script>
matches and closest
<nav>
<ul class="menu">
<li class="item active"><a href="#">Home</a></li>
<li class="item"><a href="#">About</a></li>
</ul>
</nav>
<script>
var link = document.querySelector("a");
// Does this element match a CSS selector?
console.log(link.matches("a")); // true
console.log(link.matches(".item a")); // true
console.log(link.matches(".active a")); // true
// Walk UP the tree to find the nearest ancestor matching a selector
var closestMenu = link.closest(".menu");
console.log(closestMenu.tagName); // Output: UL
var closestNav = link.closest("nav");
console.log(closestNav.tagName); // Output: NAV
</script>
data-* Attributes and the dataset API
Custom data attributes let you embed extra information in HTML without affecting layout:
<div id="product"
data-id="42"
data-name="Blue Shirt"
data-price="29.99"
data-in-stock="true">
Blue Shirt
</div>
<script>
var el = document.getElementById("product");
console.log(el.dataset.id); // "42"
console.log(el.dataset.name); // "Blue Shirt"
console.log(el.dataset.price); // "29.99"
console.log(el.dataset.inStock); // "true" (note: camelCase conversion)
// Modify a data attribute
el.dataset.price = "24.99";
// Add a new data attribute
el.dataset.discount = "10";
// Now the HTML has data-discount="10"
</script>
💡 The naming conversion:
data-in-stock→dataset.inStock(hyphen removed, next letter capitalised — same rule as CSS properties).
Input-Specific Properties
<input>, <textarea>, and <select> have special properties:
| Property | Type | Description |
|---|---|---|
value |
String | Current value of the input |
defaultValue |
String | Original value (before user changes) |
checked |
Boolean | Whether a checkbox/radio is checked |
defaultChecked |
Boolean | Original checked state |
disabled |
Boolean | Whether the input is disabled |
readOnly |
Boolean | Whether the input is read-only |
required |
Boolean | Whether the input is required |
placeholder |
String | Placeholder text |
type |
String | Input type ("text", "number", etc.) |
name |
String | Name attribute (used in form submission) |
form |
HTMLFormElement | The form this input belongs to |
validity |
ValidityState | Validation state object |
validationMessage |
String | Browser’s validation error message |
var input = document.getElementById("myInput");
input.value = "Hello"; // Set value
console.log(input.value); // Read value
input.disabled = true; // Disable it
input.readOnly = false; // Make it editable
input.focus(); // Move cursor to this field
input.select(); // Select all text in it
PHASE 2 — APPLIED EXERCISES
10. Applied Exercises
Exercise 1: Live Word Counter
Objective: Use DOM manipulation to count characters and words in a textarea in real time.
Real-world scenario: You are building a Twitter-like post composer. Users must stay under 280 characters. You need to show them their remaining character count as they type.
Warm-up mini-example:
<input type="text" oninput="showLength(this)">
<p id="len">0 characters</p>
<script>
function showLength(input) {
document.getElementById("len").innerText = input.value.length + " characters";
}
</script>
Your exercise:
Build a textarea with:
- A character counter (e.g., “245/280 characters used”)
- A word counter (e.g., “32 words”)
- A colour indicator: green when under 240, orange from 240–279, red at 280+
- A submit button that is disabled until at least 10 characters are entered
Step-by-step instructions:
- Create a
<textarea id="post">withmaxlength="280" - Add
<p id="charCount">and<p id="wordCount">display elements - Add a
<button id="submitBtn" disabled>Post</button> - Write an
oninputhandler that:- Reads
textarea.value.length - Updates the character counter text
- Splits by whitespace to count words:
text.trim().split(/\s+/).filter(Boolean).length - Changes the counter colour using
style.colorbased on length - Sets
button.disabled = (length < 10)
- Reads
Hints:
"".trim().split(/\s+/).filter(Boolean).lengthreturns0, not1. The.filter(Boolean)removes empty strings from split on an empty input.- Access the textarea’s value:
document.getElementById("post").value
Self-check questions:
- What does
filter(Boolean)do when the textarea is empty? - Why use
trim()before splitting? - What happens to
disabledwhen the user clears the textarea?
Optional what-if: Change the colour of the textarea border (not just the counter) based on length. Also display “X characters remaining” instead of “X used”.
Exercise 2: Dynamic Student Grade Table
Objective: Let the user add rows to a table using the DOM.
Real-world scenario: A school administrator app where teachers can add student grades without a page reload.
Warm-up mini-example:
<table id="t"><tr><th>Name</th><th>Grade</th></tr></table>
<button onclick="addRow()">Add Row</button>
<script>
function addRow() {
var row = document.createElement("tr");
row.innerHTML = "<td>Student</td><td>A</td>";
document.getElementById("t").appendChild(row);
}
</script>
Your exercise:
Build a grade entry system with:
- Input fields: student name, subject, grade (number 0–100)
- An “Add Student” button
- A
<table>that shows each added student - A colour-coded grade: green (≥70), orange (50–69), red (<50)
- A running class average displayed below the table
- A Delete button on each row
Step-by-step instructions:
- Create three
<input>fields with ids"name","subject","grade" - Create an empty
<table id="gradeTable">with headers - Create
<p id="average">Class Average: —</p> - In
addStudent():- Read input values
- Validate: name not empty, grade is 0–100
- Create a
<tr>with<td>cells, setting the gradetd’s background colour - Add a Delete button: set its
onclicktothis.parentElement.parentElement.remove() - After removing, recalculate the average
Grade colour logic:
var gradeNum = parseInt(grade);
var gradeCell = document.createElement("td");
gradeCell.innerText = gradeNum;
if (gradeNum >= 70) gradeCell.style.backgroundColor = "#c8f7c5";
else if (gradeNum >= 50) gradeCell.style.backgroundColor = "#fde8a0";
else gradeCell.style.backgroundColor = "#fdc5c5";
Self-check questions:
- Why do you need
parseInt()when reading a number input’s.value? - After clicking Delete, how does the average recalculate without knowing which row was removed?
- Why use
this.parentElement.parentElement.remove()instead of anidin the Delete button?
Exercise 3: CSS Theme Switcher
Objective: Toggle between light and dark themes using classList.
Real-world scenario: Most modern web apps (GitHub, VS Code, Twitter) offer a dark mode. This is typically done by toggling a class on the <body>.
Step-by-step instructions:
- In
<style>, define two themes:body.light { background-color: white; color: black; } body.dark { background-color: #1a1a2e; color: #eee; } .card { padding: 20px; border: 1px solid currentColor; margin: 10px; } - Set
<body class="light"> - Create a toggle button
- In the toggle handler:
- Use
document.body.classList.toggle("dark") - Use
document.body.classList.toggle("light") - Update the button text: “Switch to Dark” / “Switch to Light”
- Use
- Remember theme with localStorage:
localStorage.setItem("theme", "dark")and read it on page load
Self-check questions:
- Why toggle both classes instead of just adding/removing “dark”?
- What happens if a user opens the page in a new tab — will their theme preference persist?
- How would you detect the user’s operating system dark mode preference using
window.matchMedia?
Exercise 4: Animated Progress Bar
Objective: Build an animated progress bar using DOM animations.
Real-world scenario: File upload progress indicators, quiz result reveals, loading screens.
Step-by-step instructions:
- HTML: ```html
0%
2. JavaScript:
- Create a variable `progress = 0`
- Use `setInterval` every 50ms to increase `progress` by 1
- Update `bar.style.width = progress + "%"`
- Update `percent.innerText = progress + "%"`
- When progress reaches 100, call `clearInterval`
3. **Enhancement:** Change the bar colour as it fills:
- 0–40%: red
- 41–70%: orange
- 71–100%: green
**Self-check questions:**
1. Why is `clearInterval` essential — what happens without it?
2. How do you prevent the user from clicking "Start" multiple times and creating multiple intervals?
3. What does `transition: width 0.1s` in CSS do? How does this interact with the JS animation?
---
### Exercise 5: Form Validation Suite
**Objective:** Build a complete registration form with comprehensive validation.
**Fields to validate:**
| Field | Rules |
|---|---|
| Full Name | Required, letters and spaces only, 2–50 chars |
| Email | Required, valid email format |
| Phone | Required, exactly 10 digits |
| Password | Required, min 8 chars, at least 1 uppercase, 1 digit |
| Confirm Password | Must match password |
| Age | Required, number 16–120 |
| Terms checkbox | Must be checked |
**Step-by-step instructions:**
1. Create the form HTML with one `<span class="error">` next to each field
2. Write a validation function for each field
3. Call all validators on form submit
4. Show error messages next to each invalid field (not alert boxes)
5. Highlight invalid inputs with a red border using `classList.add("invalid")` and a CSS rule `.invalid { border: 2px solid red; }`
6. Clear error messages and borders for valid fields
**Hint for password strength check:**
```javascript
function validatePassword(pass) {
var hasUpper = /[A-Z]/.test(pass);
var hasDigit = /[0-9]/.test(pass);
var hasMinLength = pass.length >= 8;
return hasUpper && hasDigit && hasMinLength;
}
PHASE 3 — PROJECT SIMULATION
11. Project Simulation: Interactive Student Dashboard
Project Overview
You are a junior front-end developer at EduTrack, a school management software company. Your task is to build a client-side Student Performance Dashboard — a single HTML page that teachers can use to:
- Add students and their test scores
- View a dynamically-built grade table
- Filter students by performance category
- Animate a summary panel when scores are entered
- Validate all inputs before accepting them
No frameworks. No libraries. Pure HTML + CSS + JavaScript DOM.
Stage 1: Setup & Core Logic
Goal: Build the data entry form and display students in a table.
Preview micro-example:
var students = []; // Our in-memory data store
function addStudent(name, score) {
students.push({ name: name, score: Number(score) });
renderTable();
}
function renderTable() {
var tbody = document.getElementById("studentBody");
tbody.innerHTML = ""; // Clear existing rows
students.forEach(function(student, index) {
var row = document.createElement("tr");
row.innerHTML = `
<td>${student.name}</td>
<td>${student.score}</td>
<td>${getGrade(student.score)}</td>
`;
tbody.appendChild(row);
});
}
function getGrade(score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
Expected output after Stage 1: A functional table that grows as students are added.
Milestones:
- HTML form: name input, score input (0–100), subject dropdown, Add button
- Input validation: name required, score is 0–100, duplicate names warned
- Data array stores all entries
- Table renders from array (re-rendered on every change)
- Each row has a colour-coded grade cell
- Delete button on each row removes from array and re-renders
Stage 2: Adding Features
Goal: Add filtering, sorting, and a statistics panel.
Preview micro-example — filtering:
function filterStudents(category) {
var filtered;
if (category === "all") {
filtered = students;
} else if (category === "passing") {
filtered = students.filter(function(s) { return s.score >= 60; });
} else if (category === "failing") {
filtered = students.filter(function(s) { return s.score < 60; });
} else if (category === "excellent") {
filtered = students.filter(function(s) { return s.score >= 90; });
}
renderTable(filtered); // Pass the filtered list to the renderer
}
Expected output after Stage 2: Buttons that instantly filter the table. A stats panel showing count, average, highest, and lowest scores.
Milestones:
- Filter buttons: All / Passing (≥60) / Failing (<60) / Excellent (≥90)
- Sort by name (A–Z) or score (high–low, low–high)
- Statistics panel showing:
- Total students
- Class average (to 1 decimal place)
- Highest scorer + name
- Lowest scorer + name
- Count per grade letter (A, B, C, D, F)
- Statistics panel animates in (fade from opacity 0 to 1) when first student is added
- Export button that generates a summary
<pre>block with all data formatted as text
Stage 3: Displaying Results
Goal: Add a visual grade distribution bar chart using DOM animation.
Preview micro-example — drawing bars:
function drawChart() {
var grades = { A: 0, B: 0, C: 0, D: 0, F: 0 };
students.forEach(function(s) {
grades[getGrade(s.score)]++;
});
var total = students.length;
Object.keys(grades).forEach(function(letter) {
var barEl = document.getElementById("bar-" + letter);
var percent = total > 0 ? (grades[letter] / total) * 100 : 0;
barEl.style.width = "0%"; // Reset
// Animate to actual width
setTimeout(function() {
barEl.style.transition = "width 0.8s ease";
barEl.style.width = percent + "%";
}, 50);
document.getElementById("label-" + letter).innerText =
letter + ": " + grades[letter] + " students";
});
}
Each bar structure:
<div style="margin-bottom: 8px;">
<span id="label-A" style="display:inline-block; width:100px;">A: 0 students</span>
<div style="display:inline-block; background:#ddd; width:300px; height:20px;">
<div id="bar-A" style="background:#4CAF50; height:100%; width:0%;"></div>
</div>
</div>
Expected output after Stage 3: When students are added or removed, the bar chart animates to reflect the new grade distribution.
Full Project Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>EduTrack — Student Dashboard</title>
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 0 20px; }
h1 { color: #2c3e50; }
.form-group { margin-bottom: 10px; }
.form-group label { display: inline-block; width: 100px; }
input, select { padding: 6px; margin: 4px; border: 1px solid #ccc; border-radius: 4px; }
input.invalid { border-color: red; }
.error { color: red; font-size: 0.85em; }
button { padding: 8px 16px; cursor: pointer; border: none; border-radius: 4px; }
.btn-primary { background: #3498db; color: white; }
.btn-danger { background: #e74c3c; color: white; font-size: 0.8em; padding: 4px 8px; }
.btn-filter { background: #ecf0f1; color: #2c3e50; margin: 2px; }
.btn-filter.active { background: #3498db; color: white; }
table { border-collapse: collapse; width: 100%; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #2c3e50; color: white; }
tr:nth-child(even) { background: #f9f9f9; }
.grade-A { background: #c8f7c5 !important; }
.grade-B { background: #d5f5e3 !important; }
.grade-C { background: #fef9e7 !important; }
.grade-D { background: #fde8a0 !important; }
.grade-F { background: #fdc5c5 !important; }
#statsPanel { opacity: 0; transition: opacity 1s; margin-top: 20px; padding: 15px; background: #ecf0f1; border-radius: 8px; }
#statsPanel.visible { opacity: 1; }
.chart-bar-bg { display: inline-block; background: #ddd; width: 250px; height: 18px; vertical-align: middle; }
.chart-bar { height: 100%; width: 0%; transition: width 0.8s ease; }
.chart-label { display: inline-block; width: 120px; font-size: 0.9em; }
</style>
</head>
<body>
<h1>📊 EduTrack — Student Performance Dashboard</h1>
<!-- SECTION 1: Entry Form -->
<section>
<h2>Add Student</h2>
<div class="form-group">
<label>Name:</label>
<input type="text" id="studentName" placeholder="Full name">
<span class="error" id="nameError"></span>
</div>
<div class="form-group">
<label>Subject:</label>
<select id="subject">
<option value="Math">Math</option>
<option value="English">English</option>
<option value="Science">Science</option>
<option value="History">History</option>
</select>
</div>
<div class="form-group">
<label>Score (0–100):</label>
<input type="number" id="score" min="0" max="100" placeholder="0–100">
<span class="error" id="scoreError"></span>
</div>
<button class="btn-primary" onclick="handleAdd()">+ Add Student</button>
</section>
<!-- SECTION 2: Filter Controls -->
<section style="margin-top: 20px;">
<strong>Filter:</strong>
<button class="btn-filter active" onclick="setFilter('all', this)">All</button>
<button class="btn-filter" onclick="setFilter('passing', this)">Passing (≥60)</button>
<button class="btn-filter" onclick="setFilter('excellent', this)">Excellent (≥90)</button>
<button class="btn-filter" onclick="setFilter('failing', this)">Failing (<60)</button>
<strong style="margin-left: 20px;">Sort:</strong>
<button class="btn-filter" onclick="setSort('name')">Name A–Z</button>
<button class="btn-filter" onclick="setSort('score-high')">Score ↓</button>
<button class="btn-filter" onclick="setSort('score-low')">Score ↑</button>
</section>
<!-- SECTION 3: Grade Table -->
<table id="gradeTable">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Subject</th>
<th>Score</th>
<th>Grade</th>
<th>Action</th>
</tr>
</thead>
<tbody id="studentBody">
<tr><td colspan="6" style="text-align:center; color:#aaa;">No students yet.</td></tr>
</tbody>
</table>
<!-- SECTION 4: Statistics Panel (animates in) -->
<div id="statsPanel">
<h3>📈 Class Statistics</h3>
<div id="statsContent"></div>
<h4>Grade Distribution</h4>
<div id="gradeChart"></div>
</div>
<script>
var students = [];
var currentFilter = "all";
var currentSort = "none";
var statsShown = false;
// ─── Validation ───────────────────────────────────────
function validateInputs(name, score) {
var valid = true;
document.getElementById("nameError").innerText = "";
document.getElementById("scoreError").innerText = "";
document.getElementById("studentName").classList.remove("invalid");
document.getElementById("score").classList.remove("invalid");
if (name.trim() === "") {
document.getElementById("nameError").innerText = "Name is required.";
document.getElementById("studentName").classList.add("invalid");
valid = false;
}
var scoreNum = Number(score);
if (score === "" || isNaN(scoreNum) || scoreNum < 0 || scoreNum > 100) {
document.getElementById("scoreError").innerText = "Enter a score between 0 and 100.";
document.getElementById("score").classList.add("invalid");
valid = false;
}
return valid;
}
// ─── Add Student ──────────────────────────────────────
function handleAdd() {
var name = document.getElementById("studentName").value;
var subject = document.getElementById("subject").value;
var score = document.getElementById("score").value;
if (!validateInputs(name, score)) return;
students.push({
id: Date.now(),
name: name.trim(),
subject: subject,
score: Number(score)
});
// Clear inputs
document.getElementById("studentName").value = "";
document.getElementById("score").value = "";
render();
// Animate stats panel in on first student
if (!statsShown) {
statsShown = true;
setTimeout(function() {
document.getElementById("statsPanel").classList.add("visible");
}, 100);
}
}
// ─── Delete Student ───────────────────────────────────
function deleteStudent(id) {
students = students.filter(function(s) { return s.id !== id; });
render();
}
// ─── Grading ──────────────────────────────────────────
function getGrade(score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
// ─── Filter & Sort ────────────────────────────────────
function setFilter(filter, btn) {
currentFilter = filter;
document.querySelectorAll(".btn-filter").forEach(function(b) {
b.classList.remove("active");
});
btn.classList.add("active");
render();
}
function setSort(sort) {
currentSort = sort;
render();
}
function applyFilterSort(list) {
// Filter
var filtered = list.filter(function(s) {
if (currentFilter === "passing") return s.score >= 60;
if (currentFilter === "failing") return s.score < 60;
if (currentFilter === "excellent") return s.score >= 90;
return true;
});
// Sort
if (currentSort === "name") {
filtered.sort(function(a, b) { return a.name.localeCompare(b.name); });
} else if (currentSort === "score-high") {
filtered.sort(function(a, b) { return b.score - a.score; });
} else if (currentSort === "score-low") {
filtered.sort(function(a, b) { return a.score - b.score; });
}
return filtered;
}
// ─── Render Table ─────────────────────────────────────
function renderTable(list) {
var tbody = document.getElementById("studentBody");
tbody.innerHTML = "";
if (list.length === 0) {
tbody.innerHTML = "<tr><td colspan='6' style='text-align:center; color:#aaa;'>No students match the filter.</td></tr>";
return;
}
list.forEach(function(student, index) {
var grade = getGrade(student.score);
var row = document.createElement("tr");
row.className = "grade-" + grade;
row.innerHTML =
"<td>" + (index + 1) + "</td>" +
"<td>" + student.name + "</td>" +
"<td>" + student.subject + "</td>" +
"<td>" + student.score + "</td>" +
"<td><strong>" + grade + "</strong></td>" +
"<td><button class='btn-danger' data-id='" + student.id + "'>Delete</button></td>";
row.querySelector(".btn-danger").addEventListener("click", function() {
deleteStudent(Number(this.getAttribute("data-id")));
});
tbody.appendChild(row);
});
}
// ─── Render Stats ─────────────────────────────────────
function renderStats() {
if (students.length === 0) {
document.getElementById("statsContent").innerHTML = "<p>Add students to see statistics.</p>";
document.getElementById("gradeChart").innerHTML = "";
return;
}
var scores = students.map(function(s) { return s.score; });
var avg = (scores.reduce(function(a, b) { return a + b; }, 0) / scores.length).toFixed(1);
var highest = Math.max.apply(null, scores);
var lowest = Math.min.apply(null, scores);
var topStudent = students.find(function(s) { return s.score === highest; });
var bottomStudent = students.find(function(s) { return s.score === lowest; });
var gradeCounts = { A: 0, B: 0, C: 0, D: 0, F: 0 };
students.forEach(function(s) { gradeCounts[getGrade(s.score)]++; });
document.getElementById("statsContent").innerHTML =
"<p>👥 <strong>Total Students:</strong> " + students.length + "</p>" +
"<p>📊 <strong>Class Average:</strong> " + avg + "</p>" +
"<p>🏆 <strong>Top Scorer:</strong> " + topStudent.name + " (" + highest + ")</p>" +
"<p>📉 <strong>Lowest Scorer:</strong> " + bottomStudent.name + " (" + lowest + ")</p>";
// Build animated grade chart
var chartHTML = "";
var colors = { A: "#4CAF50", B: "#8BC34A", C: "#FFC107", D: "#FF9800", F: "#F44336" };
Object.keys(gradeCounts).forEach(function(letter) {
var pct = students.length > 0 ? (gradeCounts[letter] / students.length * 100) : 0;
chartHTML +=
"<div style='margin-bottom:8px;'>" +
"<span class='chart-label'>" + letter + ": " + gradeCounts[letter] + " student(s)</span>" +
"<div class='chart-bar-bg'><div id='bar-" + letter + "' class='chart-bar' style='background:" + colors[letter] + ";'></div></div>" +
"</div>";
});
document.getElementById("gradeChart").innerHTML = chartHTML;
// Animate bars
Object.keys(gradeCounts).forEach(function(letter) {
var pct = students.length > 0 ? (gradeCounts[letter] / students.length * 100) : 0;
setTimeout(function() {
var bar = document.getElementById("bar-" + letter);
if (bar) bar.style.width = pct + "%";
}, 50);
});
}
// ─── Master Render ────────────────────────────────────
function render() {
var displayList = applyFilterSort(students);
renderTable(displayList);
renderStats();
}
</script>
</body>
</html>
Reflection Questions
-
Why do we re-render the entire table on every change instead of just modifying one row? What are the pros and cons of this “full re-render” approach vs a “targeted update” approach?
-
The statistics panel fades in on the first student added. How would you make it fade back out if all students are deleted?
-
The filter buttons only filter the displayed table, not the underlying data array. Why is this the right approach? What would go wrong if you filtered the array itself?
-
How would this project work in a real company? What would need to change if student data had to be saved to a database and persist across page reloads?
-
The Delete button uses
data-idinstead of the student’s array index. Why is this safer than using the index directly?
Optional Advanced Features
- Edit in place: Clicking a student’s name makes it editable inline using
contenteditable - Export to CSV: A button that builds a CSV string and triggers a download using
URL.createObjectURL - LocalStorage persistence: Save the
studentsarray tolocalStorageon every change, restore it on page load - Search filter: A text input that filters students by name in real time using
querySelectorAllandclassList - Drag-to-reorder: Implement drag-and-drop row reordering using
draggableand the drag events
12. Completion Checklist & Summary
Completion Checklist
| # | Item | Covered? |
|---|---|---|
| 1 | The DOM concept — what it is and why it exists | ✅ |
| 2 | document as the root object |
✅ |
| 3 | getElementById, getElementsByTagName, getElementsByClassName |
✅ |
| 4 | querySelector and querySelectorAll |
✅ |
| 5 | Tree navigation: parent, children, siblings | ✅ |
| 6 | innerHTML, innerText, textContent differences |
✅ |
| 7 | getAttribute, setAttribute, removeAttribute |
✅ |
| 8 | createElement, appendChild, insertBefore, removeChild, replaceChild |
✅ |
| 9 | outerHTML, cloneNode, remove |
✅ |
| 10 | insertAdjacentHTML and its four positions |
✅ |
| 11 | element.style and camelCase CSS property names |
✅ |
| 12 | classList — add, remove, toggle, contains, replace |
✅ |
| 13 | getComputedStyle vs inline styles |
✅ |
| 14 | Form validation: required fields, email, numbers, real-time | ✅ |
| 15 | setInterval, clearInterval, requestAnimationFrame |
✅ |
| 16 | Building a sliding animation with boundary detection | ✅ |
| 17 | document properties: title, URL, forms, images, links, cookie |
✅ |
| 18 | DocumentFragment performance pattern |
✅ |
| 19 | Element position/size: offsetWidth, getBoundingClientRect | ✅ |
| 20 | dataset and data-* attributes |
✅ |
| 21 | matches and closest |
✅ |
| 22 | Input-specific properties: value, checked, disabled | ✅ |
| 23 | Applied exercises with real-world scenarios | ✅ |
| 24 | Full mini-project with validation, animation, and DOM construction | ✅ |
| 25 | Common beginner errors highlighted throughout | ✅ |
Common Beginner Errors — Quick Reference
| Mistake | Why it happens | Fix |
|---|---|---|
getElementById returns null |
Script runs before the element exists in the DOM | Place <script> at the end of <body>, or use DOMContentLoaded |
style.left returns "" not "0px" |
Initial style is not set inline | Set style.left = "0px" in HTML, or check for "" and default to 0 |
parseInt returns NaN |
Style value is empty string | Always provide a fallback: parseInt(el.style.left) \|\| 0 |
| Multiple intervals stacking | startMove() called multiple times |
Always call clearInterval(timer) before setInterval |
innerHTML XSS risk |
User input inserted as HTML | Use innerText or sanitise HTML |
getElementsByTagName not updating |
Expected live, got stale index | HTMLCollections are live, but indices can shift; loop in reverse when removing |
querySelectorAll result has no .forEach in IE |
NodeList in old browsers | Use Array.from(list).forEach(...) for compatibility |
document.write destroying the page |
Called after load | Use innerHTML instead for post-load content insertion |
Forgetting return false in onsubmit |
Form submits even on validation failure | Always return false (or event.preventDefault()) when validation fails |
Using id that contains spaces |
Browsers allow it but JS selectors break | Never put spaces in id values |
One-Sentence Summary
The HTML DOM is JavaScript’s live map of your webpage — master its methods for finding, changing, creating, and animating elements, and you gain complete programmatic control over everything users see and interact with.
Tutorial generated by AI_TUTORIAL_GENERATOR · Source: W3Schools JavaScript HTML DOM series (9 pages) · Format: Markdown