DOM Programming
The Document Object Model, commonly known as the DOM, is a crucial concept in web development. It represents the structure of a webpage in a way that programming languages, primarily JavaScript, can interact with. Essentially, the DOM turns a static HTML document into a dynamic structure, allowing for real-time changes and interactions.
Why is the DOM Important?
Interactivity:
Without the DOM, web pages would be static. The DOM allows for real-time changes, meaning that user interactions, animations, and dynamic content updates are possible because of it.
Structure:
The DOM provides a hierarchical representation of a webpage. This hierarchy is tree-like, with the document as the root, and every HTML element (like paragraphs, links, and images) as branches and leaves.
Language-Neutral:
While the DOM is most commonly accessed using JavaScript, it’s designed to be independent of any particular programming language. This design ensures that various languages can interact with web content in a standardized way.
The DOM Tree Structure
Every HTML document becomes a tree of nodes in the DOM:
Each element is a node, and these nodes have relationships:
- Parent: The node directly above
- Child: The node directly below
- Sibling: Nodes at the same level
Objects from the Browser
Every browser provides a set of objects that allow developers to interact with the DOM and other browser functionalities:
window:
Represents the browser’s window. It’s the global object in client-side JavaScript and provides functions, methods, and properties to work with the current session.
navigator:
Provides information about the browser, like its version and the operating system it’s running on.
history:
Allows manipulation of the browser session history, enabling actions like moving forward or backward through the user’s history.
screen:
Offers details about the user’s screen, such as its width, height, and color depth.
location:
Represents the current URL and provides methods to redirect the user or refresh the page.
document:
The main entry point to the webpage’s content. It represents the structure of the webpage and provides methods to add, modify, or delete elements.
Global Functions:
Functions like alert() (which displays an alert box), setTimeout() (which calls a function after a delay), or setInterval() (which calls a function at regular intervals) are part of the global scope and are often used for various tasks.
HTML Elements as Objects:
Every HTML element on a webpage is represented as an object in the DOM. For instance, a button in HTML becomes an HTMLButtonElement in the DOM. This object-oriented representation allows for precise interactions and modifications.
Frameworks and Libraries
While developers can interact with the DOM using raw JavaScript (often called “vanilla JavaScript”), many choose to use libraries (like jQuery) or frameworks (like React, Vue, or Angular). These tools provide abstractions over the raw DOM APIs, making it easier to perform common tasks, ensure cross-browser compatibility, and structure code in a more maintainable way.
However, modern vanilla JavaScript is very capable, and understanding the raw DOM API is essential even when using frameworks.
Finding Elements
The document object has several methods that can be used to find elements:
Modern methods (recommended):
querySelector():
Finds and returns the first element that matches a CSS selector. Returns null if no match is found.
let header = document.querySelector("#ov"); // By ID
let button = document.querySelector(".btn"); // By class
let para = document.querySelector("p"); // By tag
let specific = document.querySelector("div.container > p:first-child"); // Complex selector
querySelectorAll():
Returns a static NodeList of all elements matching a CSS selector. Does not update dynamically.
Legacy methods (still useful):
getElementById():
Finds and returns the first element with the specified id. Returns null if no match is found.
getElementsByClassName():
Returns a live HTMLCollection of all elements with the specified class name.
getElementsByTagName():
Returns a live HTMLCollection of all elements with the specified tag name.
getElementsByName():
Returns a live NodeList of elements with a given name attribute. Commonly used for form inputs.
Traversal methods:
closest():
Returns the nearest ancestor (or itself) that matches a given CSS selector. Returns null if no match is found.
matches():
Returns true if the element matches the specified CSS selector, otherwise false.
querySelector vs getElementById
querySelector(“#id”) is more flexible (can use any CSS selector) but slightly slower.
getElementById(“id”) is faster but only works for IDs.
For most use cases, the performance difference is negligible. Use querySelector for consistency.
Example: Finding Elements
Given the following HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1 id="ov">Header</h1>
<h2>Subheader</h2>
<p>Long text</p>
<ul>
<li class="item">list item 1</li>
<li>list item 2</li>
<li class="item">list item 3</li>
</ul>
<button id="button">Button</button>
<script src="app.js"></script>
</body>
</html>
The JavaScript code to find elements is:
(function () {
// Find header by ID
let header = document.getElementById("ov");
console.dir(header);
// Find button with querySelector
let button = document.querySelector("#button");
console.dir(button);
// Find all li elements
let li = document.querySelectorAll("li");
console.dir(li); // NodeList (array-like)
// Find only items with class "item"
let items = document.querySelectorAll(".item");
console.log(items.length); // 2
})();
Updating Elements
Depending on the type of element, different properties can be used to update content and appearance:
Content properties:
innerText:
Represents the text content of an element, excluding hidden elements. Updates dynamically with the rendered content.
innerHTML:
Represents the HTML content of an element, including tags. Can be used to set or get the entire content of an element.
Security warning: innerHTML
Be careful using innerHTML with user-provided content, as it can lead to XSS (Cross-Site Scripting) attacks. Always sanitize user input or use textContent instead.
textContent:
Similar to innerText but includes all text, even from hidden elements, and doesn’t account for CSS styles. Faster than innerText.
Styling:
style:
Provides access to the inline styles of an element. Properties are accessed as camelCase (e.g., element.style.backgroundColor).
element.style.color = "red";
element.style.backgroundColor = "#f0f0f0";
element.style.fontSize = "20px";
classList:
Provides methods to manipulate the classes of an element:
add()- Add a classremove()- Remove a classtoggle()- Toggle a classcontains()- Check if class existsreplace()- Replace one class with another
element.classList.add("active");
element.classList.remove("hidden");
element.classList.toggle("selected");
if (element.classList.contains("active")) {
console.log("Element is active");
}
Attributes:
setAttribute() / getAttribute() / removeAttribute():
element.setAttribute("data-id", "123");
let id = element.getAttribute("data-id");
element.removeAttribute("data-id");
Direct property access:
element.id = "myId";
element.className = "myClass"; // Replaces all classes
link.href = "https://example.com";
input.value = "New value";
input.disabled = true;
dataset:
Provides access to all data-* attributes of an element as a DOMStringMap.
// <div data-user-id="123" data-user-name="John"></div>
let userId = element.dataset.userId; // "123"
let userName = element.dataset.userName; // "John"
element.dataset.newAttribute = "value"; // Creates data-new-attribute
Form elements:
value:
Represents the current value of an input, textarea, or select element.
let input = document.querySelector("#username");
console.log(input.value);
input.value = "New value";
Navigation properties:
parentElement:
Returns the parent element of the current element. Returns null if there’s no parent.
children:
Returns a live HTMLCollection of only the element’s child elements (ignores text and comment nodes).
firstElementChild / lastElementChild:
Returns the first/last child element of a parent element.
nextElementSibling / previousElementSibling:
Returns the next/previous sibling element in the DOM tree.
let parent = element.parentElement;
let children = parent.children;
let nextSibling = element.nextElementSibling;
Example: Updating Elements
To update the header, change the color of all list items to red, and add a class to the button:
(function () {
// New header
let h = document.querySelector("#ov");
h.innerHTML = "New Header";
// Color for all li
let li = document.querySelectorAll("li");
for (let i = 0; i < li.length; i++) {
li[i].style.color = "red";
}
// Modern alternative with forEach
li.forEach(item => {
item.style.color = "red";
});
// Add class to button
document.querySelector("#button").classList.add("myClass");
})();
Events
Events are actions or occurrences you can respond to in your code. Understanding events is crucial for creating interactive web applications.
Event Types
Mouse Events:
click- Element is clickeddblclick- Element is double-clickedmousedown- Mouse button is pressedmouseup- Mouse button is releasedmousemove- Mouse moves over elementmouseover- Mouse enters elementmouseout- Mouse leaves elementmouseenter- Mouse enters element (doesn’t bubble)mouseleave- Mouse leaves element (doesn’t bubble)
Keyboard Events:
keydown- Key is pressed downkeyup- Key is releasedkeypress- Key is pressed (deprecated, use keydown)
Form Events:
submit- Form is submittedchange- Value changes (for select, checkbox, radio)input- Value changes (for text inputs, fires on every keystroke)focus- Element gains focusblur- Element loses focusreset- Form is reset
Window Events:
load- Page finishes loadingDOMContentLoaded- HTML is loaded and parsed (doesn’t wait for stylesheets/images)resize- Window is resizedscroll- Page is scrolledbeforeunload- Before page is unloaded (useful for unsaved changes warning)
Adding Event Listeners
There are two main ways to add event listeners:
Event handler properties (old way):
Assign a handler directly to the element’s event property. This overwrites any existing handler.
button.onclick = function() {
console.log("Clicked!");
};
// Overwrites the previous handler
button.onclick = function() {
console.log("This will run instead");
};
addEventListener (recommended):
This method attaches a handler without overwriting existing ones. You can attach multiple handlers to the same event.
button.addEventListener("click", function() {
console.log("Handler 1");
});
button.addEventListener("click", function() {
console.log("Handler 2");
});
// Both handlers will run
The Event Object
Every event handler receives an event object with information about the event:
element.addEventListener("click", function(event) {
console.log(event.type); // "click"
console.log(event.target); // Element that triggered the event
console.log(event.currentTarget); // Element with the listener
console.log(event.clientX); // Mouse X coordinate
console.log(event.clientY); // Mouse Y coordinate
// Prevent default behavior
event.preventDefault();
// Stop event from bubbling up
event.stopPropagation();
});
Important event object properties:
event.target- The element that triggered the eventevent.currentTarget- The element with the event listener (same asthis)event.type- The type of event (“click”, “keydown”, etc.)event.preventDefault()- Prevents default behavior (e.g., form submission, link navigation)event.stopPropagation()- Stops event from bubbling to parent elements
Event Bubbling and Capturing
Events in the DOM propagate in three phases:
- Capturing phase: Event travels from window down to target
- Target phase: Event reaches the target element
- Bubbling phase: Event bubbles up from target to window
Most events bubble up through the DOM tree:
document.querySelector("#child").addEventListener("click", function() {
console.log("Child clicked");
});
document.querySelector("#parent").addEventListener("click", function() {
console.log("Parent clicked");
});
// Clicking child logs:
// "Child clicked"
// "Parent clicked" (bubbling!)
Stop bubbling:
element.addEventListener("click", function(event) {
event.stopPropagation(); // Prevents bubbling to parent
console.log("Only this handler runs");
});
Event Delegation
Instead of adding listeners to many elements, add one listener to a parent and use event.target:
// Bad: Adding listener to each item
document.querySelectorAll("li").forEach(item => {
item.addEventListener("click", function() {
console.log("Item clicked");
});
});
// Good: Event delegation
document.querySelector("ul").addEventListener("click", function(event) {
if (event.target.tagName === "LI") {
console.log("Item clicked:", event.target.textContent);
}
});
Benefits of event delegation:
- Works for dynamically added elements
- Better performance (fewer event listeners)
- Cleaner code
Example: Event Handling
To change the header color to red when the button is clicked and to black when the header is clicked:
(function () {
// When the button is clicked
let button = document.querySelector("#button");
button.addEventListener("click", function() {
document.querySelector("#ov").style.color = "red";
});
// When the header is clicked
let header = document.querySelector("#ov");
header.addEventListener("click", function(event) {
this.style.color = "black";
// Or use event.currentTarget.style.color = "black";
});
})();
Form Handling Example
let form = document.querySelector("#myForm");
form.addEventListener("submit", function(event) {
event.preventDefault(); // Prevent form submission
let username = document.querySelector("#username").value;
let email = document.querySelector("#email").value;
// Validation
if (username.length < 3) {
alert("Username must be at least 3 characters");
return;
}
if (!email.includes("@")) {
alert("Invalid email address");
return;
}
// Submit form data
console.log("Form data:", { username, email });
});
Creating and Removing Elements
The DOM allows dynamic manipulation of elements by providing methods for creating, modifying, and removing elements from the document.
Creating Elements
New elements can be created using the createElement() method. This method generates an element of the specified type, but it does not immediately appear in the DOM until it is explicitly added.
let newDiv = document.createElement("div");
let newPara = document.createElement("p");
let newButton = document.createElement("button");
Adding Content
Once created, you can add content and attributes:
let newPara = document.createElement("p");
newPara.textContent = "This is a new paragraph";
newPara.className = "highlight";
newPara.id = "myPara";
Adding Elements to the DOM
Once created, elements can be added to the DOM tree using various methods:
appendChild():
Adds an element as the last child of a parent node.
let parent = document.querySelector("#container");
let child = document.createElement("p");
child.textContent = "New paragraph";
parent.appendChild(child);
insertBefore():
Inserts an element at a specific position relative to another child.
let parent = document.querySelector("#container");
let newElement = document.createElement("p");
let referenceElement = document.querySelector("#reference");
parent.insertBefore(newElement, referenceElement);
insertAdjacentElement():
Provides precise positioning around an existing element.
Positions:
"beforebegin"- Before the element itself"afterbegin"- Just inside the element, before its first child"beforeend"- Just inside the element, after its last child"afterend"- After the element itself
insertAdjacentHTML():
Similar to insertAdjacentElement but takes HTML string:
Removing Elements
remove():
Directly removes the element from the DOM (modern method).
removeChild():
Removes a specific child element from its parent (legacy method).
let parent = document.querySelector("#container");
let child = document.querySelector("#myElement");
parent.removeChild(child);
replaceChild():
Replaces an existing child with a new element.
let parent = document.querySelector("#container");
let oldChild = document.querySelector("#old");
let newChild = document.createElement("p");
newChild.textContent = "Replacement";
parent.replaceChild(newChild, oldChild);
Example: Creating and Removing Elements
To add a new list item and remove the button:
(function () {
// Find ul
let ul = document.querySelector("ul");
// Create li
let li = document.createElement("li");
li.setAttribute("id", "myId");
li.textContent = "list item 4"; // Safer than innerHTML
// Add li
ul.appendChild(li);
// Remove button (modern way)
let button = document.querySelector("#button");
button.remove();
// Or legacy way:
// document.querySelector("body").removeChild(button);
})();
Creating Complex Structures
// Create a card component
let card = document.createElement("div");
card.className = "card";
let title = document.createElement("h3");
title.textContent = "Card Title";
let text = document.createElement("p");
text.textContent = "Card description";
let button = document.createElement("button");
button.textContent = "Click me";
button.addEventListener("click", function() {
alert("Button clicked!");
});
// Assemble
card.appendChild(title);
card.appendChild(text);
card.appendChild(button);
// Add to page
document.querySelector("#container").appendChild(card);
Best Practices
Use querySelector/querySelectorAll:
These methods are flexible, modern, and support all CSS selectors.
Cache DOM references:
Don’t query the DOM repeatedly in loops.
// Bad
for (let i = 0; i < 100; i++) {
document.querySelector("#container").appendChild(element);
}
// Good
let container = document.querySelector("#container");
for (let i = 0; i < 100; i++) {
container.appendChild(element);
}
Use event delegation:
For lists or dynamically added elements, add one listener to the parent instead of many listeners to children.
Prefer textContent over innerHTML:
Unless you specifically need to insert HTML, use textContent for security and performance.
Use classList instead of className:
classList methods are cleaner and safer than manipulating the className string.
Minimize DOM manipulation:
DOM operations are expensive. Build elements in memory first, then add to DOM once.
// Bad: Multiple DOM insertions
for (let i = 0; i < 100; i++) {
let item = document.createElement("li");
item.textContent = i;
list.appendChild(item); // Triggers reflow each time
}
// Good: Single DOM insertion
let fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
let item = document.createElement("li");
item.textContent = i;
fragment.appendChild(item);
}
list.appendChild(fragment); // Single reflow
Use addEventListener over event properties:
This allows multiple handlers and better control.
Always preventDefault() when needed:
For forms, links, or any default behavior you want to control.
Summary
The DOM is the programming interface that allows JavaScript to interact with and manipulate HTML documents dynamically.
Key Takeaways
What is the DOM:
- Tree structure representing HTML document
- Provides API for JavaScript to interact with web pages
- Language-neutral but primarily used with JavaScript
- Enables dynamic, interactive web applications
Finding Elements:
- Modern:
querySelector(),querySelectorAll()(recommended) - Legacy:
getElementById(),getElementsByClassName(),getElementsByTagName() - Traversal:
closest(),matches(),parentElement,children - Use CSS selectors with querySelector for flexibility
Updating Elements:
- Content:
textContent(safe),innerHTML(use carefully),innerText - Styles:
styleproperty orclassListmethods - Attributes:
setAttribute(),getAttribute(),dataset - Properties: Direct access like
element.value,element.disabled
Events:
- Adding listeners:
addEventListener()(preferred) or event properties - Event types: Mouse, keyboard, form, window events
- Event object: Contains info about the event (
target,type, etc.) - Methods:
preventDefault(),stopPropagation() - Patterns: Event delegation for better performance
Creating and Removing:
- Create:
createElement() - Add:
appendChild(),insertBefore(),insertAdjacentElement() - Remove:
remove()(modern),removeChild()(legacy) - Replace:
replaceChild()
Best Practices:
- Cache DOM references - don’t query repeatedly
- Use event delegation for lists and dynamic content
- Prefer
textContentoverinnerHTMLfor security - Use
classListinstead of manipulatingclassName - Minimize DOM operations - batch changes when possible
- Use
addEventListener()for multiple handlers - Always
preventDefault()when controlling default behavior
Common Patterns:
- Event delegation for dynamic elements
- Form validation and submission handling
- Creating complex UI components programmatically
- Toggling classes for UI state changes
- Building elements in DocumentFragment before adding to DOM
Understanding the DOM enables you to create interactive, dynamic web applications that respond to user actions and update content in real-time.