DOM Crash Course

·

11 min read

The Document Object Model (DOM) is a tree-like representation of a web page, created by the browser when an HTML document loads. It allows programming languages like JavaScript to interact with the page's structure, content, and styles dynamically. Each HTML element (e.g., <div>, <p>) becomes a node in the DOM tree, which can be manipulated programmatically.

Let's dive deep into accessing and manipulating HTML elements, attributes, content, and styling using JavaScript:


1. Accessing Elements

getElementById()

  • Selects an element by its id attribute.

  • Returns a single element (since id is unique).

<div id="header">Header</div>
const header = document.getElementById('header');

getElementsByClassName()

  • Returns a live HTMLCollection of elements with a specific class.

  • Use [index] to access individual elements.

<div class="item">Item 1</div>
<div class="item">Item 2</div>
const items = document.getElementsByClassName('item');
console.log(items[0].textContent); // "Item 1"

getElementsByTagName()

  • Returns a live HTMLCollection of elements with the specified tag name.
<p>Paragraph 1</p>
<p>Paragraph 2</p>
const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs.length); // 2

querySelector()

  • Returns the first element matching a CSS-style selector.
const firstButton = document.querySelector('.btn'); // First element with class "btn"
const input = document.querySelector('input[type="text"]'); // First text input

querySelectorAll()

  • Returns a static NodeList of all elements matching the selector.
const allButtons = document.querySelectorAll('.btn'); // All elements with class "btn"
allButtons.forEach(button => {
  console.log(button.textContent);
});

2. Working with Attributes

setAttribute()

  • Sets the value of an attribute.
const link = document.querySelector('a');
link.setAttribute('href', 'https://google.com');
link.setAttribute('target', '_blank');

getAttribute()

  • Retrieves the value of an attribute.
const href = link.getAttribute('href'); // "https://google.com"

Direct Property Access

  • Many attributes can be accessed directly as properties:
link.href = 'https://example.com'; // Updates the href attribute
link.id = 'myLink'; // Sets the id
console.log(link.id); // "myLink"

3. Modifying Content

textContent

  • Safely sets/returns plain text inside an element (ignores HTML tags).
<div id="box"><span>Hello</span></div>
const box = document.querySelector('#box');
box.textContent = 'New text'; // Replaces inner HTML with plain text
console.log(box.textContent); // "New text"

innerText

  • Similar to textContent, but CSS-aware (returns only visible text).
console.log(box.innerText); // "Hello" (if element is visible)

innerHTML

  • Sets/returns HTML content (can introduce security risks like XSS).
box.innerHTML = '<strong>Bold text</strong>'; // Renders HTML
console.log(box.innerHTML); // "<strong>Bold text</strong>"

outerHTML

  • Includes the element itself along with its content.
console.log(box.outerHTML); // "<div id="box"><strong>Bold text</strong></div>"

4. Styling Elements

Inline Styles (.style)

  • Modify inline styles using camelCase properties:
const div = document.querySelector('div');
div.style.backgroundColor = 'blue';
div.style.fontSize = '20px';
div.style.marginTop = '10px';

classList for CSS Classes

  • Add/remove/toggle classes without overwriting existing ones:
div.classList.add('active');
div.classList.remove('inactive');
div.classList.toggle('hidden');

5. Other Key Methods

createElement() & appendChild()

  • Create and add elements dynamically:
const newDiv = document.createElement('div');
newDiv.textContent = 'New Div';
document.body.appendChild(newDiv);

remove() & removeChild()

  • Remove elements:
newDiv.remove(); // Modern method
// Older method:
const parent = newDiv.parentElement;
parent.removeChild(newDiv);

6. Attribute vs. Property

  • Attributes: Defined in HTML (e.g., <input required>).

  • Properties: Live values in the DOM (e.g., input.checked).

<input type="checkbox" id="check" checked>
const check = document.getElementById('check');
console.log(check.getAttribute('checked')); // "checked" (string)
console.log(check.checked); // true (boolean)

7. Security & Best Practices

  • Avoid innerHTML for untrusted content (risk of XSS attacks).

  • Prefer textContent over innerText for performance.

  • Cache frequently accessed elements:

const cachedElement = document.getElementById('header'); // Store in a variable

Comparison Table: Content Properties

PropertyReturnsNotes
textContentPlain text (all content)Fast, ignores HTML
innerTextVisible text (CSS-aware)Slower, skips hidden elements
innerHTMLHTML contentSecurity risk if misused
outerHTMLHTML of element + its contentReplaces the entire element

Example: Dynamic Styling

const button = document.querySelector('.btn');
button.addEventListener('click', () => {
  button.style.backgroundColor = 'red';
  button.classList.toggle('clicked');
});

By mastering these methods, you can fully control the DOM to create dynamic, interactive web pages!

Projects can be done with this learnings

Here are 8 practical projects to solidify your DOM manipulation skills, ranging from beginner to intermediate level:


1. Interactive To-Do List

Tech: HTML, CSS, JavaScript
Features:

  • Add/delete tasks

  • Mark tasks as complete

  • Filter tasks (all/active/completed)

  • Local storage persistence
    DOM Skills:

    • createElement(), appendChild()

    • querySelectorAll() for task buttons

    • classList.toggle() for completed tasks

    • localStorage integration


2. Live Character Counter

Tech: Text input, real-time updates
Features:

  • Display remaining characters as user types

  • Change color when limit is exceeded
    DOM Skills:

    • input event listener

    • textContent updates

    • Conditional styling with classList


3. Dynamic Quiz App

Tech: Forms, timers
Features:

  • Multiple-choice questions

  • Score tracking

  • Progress bar

  • Result summary
    DOM Skills:

    • forms and submit event

    • innerHTML for question rendering

    • setInterval() for timers


4. Theme Switcher

Tech: CSS Variables, buttons
Features:

  • Toggle between light/dark themes

  • Custom color picker

  • Save preference to localStorage
    DOM Skills:

    • style.setProperty() for CSS variables

    • data-* attributes for theme storage

    • change event listeners


Tech: Grid layout, overlay
Features:

  • Thumbnail grid

  • Click to enlarge in modal

  • Previous/next navigation

  • Caption display
    DOM Skills:

    • click event delegation

    • display: none/block toggling

    • dataset for image metadata


6. Real-Time Form Validation

Tech: Regex, error messages
Features:

  • Instant feedback on input fields

  • Password strength meter

  • Submit button enable/disable
    DOM Skills:

    • keyup/blur events

    • classList.add() for error states

    • Dynamic <span> error messages


7. Drag-and-Drop Task Board

Tech: Drag API, flexbox
Features:

  • Columns (Todo/In Progress/Done)

  • Drag tasks between columns

  • Animation on drop
    DOM Skills:

    • draggable attribute

    • dragstart/dragend events

    • insertBefore() for repositioning


8. Pomodoro Timer

Tech: Counters, audio
Features:

  • Adjustable work/break durations

  • Circular progress indicator

  • Notification sound
    DOM Skills:

    • setTimeout()/setInterval()

    • SVG/CSS animation updates

    • <audio> element control


Bonus Challenges:

  1. Add keyboard shortcuts to projects

  2. Implement undo/redo functionality

  3. Create a responsive mobile menu

  4. Build a password visibility toggler

Each project will help you master specific DOM manipulation techniques while creating portfolio-worthy apps. Start simple (To-Do List) and gradually tackle more complex projects (Drag-and-Drop Board)! 🚀

Let's build a DOM Tree Explorer project that combines traversal methods (parent, child, sibling) and element manipulation (append, prepend, insertBefore). Users will interact with a visual tree structure and manipulate it using various DOM methods.


Project: DOM Tree Navigator

Features:

  1. Visual tree structure with nodes

  2. Highlight parent/children/siblings on click

  3. Add elements at different positions

  4. Full traversal controls

  5. Real-time DOM updates


HTML Structure

<div class="container">
  <div class="controls">
    <button onclick="addChild()">Add Child</button>
    <button onclick="addSibling('after')">Add Next Sibling</button>
    <button onclick="addSibling('before')">Add Prev Sibling</button>
    <button onclick="removeNode()">Remove Node</button>
  </div>

  <div id="tree" class="tree">
    <div class="node root">Root</div>
  </div>
</div>

CSS Styling

.tree {
  padding: 20px;
}

.node {
  padding: 10px;
  margin: 5px;
  border: 2px solid #333;
  cursor: pointer;
  background: #fff;
}

.selected {
  background: #b3e5fc;
  border-color: #039be5;
}

.children {
  margin-left: 30px;
  border-left: 2px dashed #ccc;
}

JavaScript Logic

let selectedNode = null;

// Initialize tree
document.querySelectorAll('.node').forEach(node => {
  node.addEventListener('click', handleNodeClick);
});

function handleNodeClick(e) {
  // Clear previous selection
  document.querySelectorAll('.node').forEach(n => n.classList.remove('selected'));

  // Set new selection
  selectedNode = e.target;
  selectedNode.classList.add('selected');
  e.stopPropagation();
}

// Add child node
function addChild() {
  if (!selectedNode) return;

  const childrenContainer = selectedNode.nextElementSibling || createChildrenContainer();
  const newNode = createNode('Child');

  if (!selectedNode.nextElementSibling) {
    selectedNode.parentNode.insertBefore(childrenContainer, selectedNode.nextSibling);
  }
  childrenContainer.appendChild(newNode);
}

// Add sibling node
function addSibling(position) {
  if (!selectedNode || selectedNode.classList.contains('root')) return;

  const newNode = createNode('Sibling');
  position === 'after' 
    ? selectedNode.parentNode.insertBefore(newNode, selectedNode.nextSibling)
    : selectedNode.parentNode.insertBefore(newNode, selectedNode);
}

// Remove node
function removeNode() {
  if (!selectedNode || selectedNode.classList.contains('root')) return;

  const parent = selectedNode.parentNode;
  parent.removeChild(selectedNode);
  selectedNode = null;
}

// Helper: Create node element
function createNode(text) {
  const node = document.createElement('div');
  node.className = 'node';
  node.textContent = text;
  node.addEventListener('click', handleNodeClick);
  return node;
}

// Helper: Create children container
function createChildrenContainer() {
  const div = document.createElement('div');
  div.className = 'children';
  return div;
}

Key DOM Concepts Demonstrated

  1. Traversal:

    • parentNode: Access direct parent

    • nextElementSibling: Get next sibling

    • previousElementSibling: Get previous sibling

    • children: Access child elements

  2. Insertion Methods:

    • appendChild(): Add to end of children

    • insertBefore(): Insert at specific position

    • parentNode.insertBefore(): Relative positioning

  3. Element Manipulation:

    • classList management

    • createElement() for dynamic nodes

    • Event delegation for dynamic elements

  4. Position Detection:

    • Checking for existing children containers

    • Handling edge cases (root node protection)


How to Use

  1. Click any node to select it (blue highlight)

  2. Use buttons to:

    • Add child nodes (nested under selected)

    • Add siblings before/after selected node

    • Remove nodes (except root)

  3. Observe the tree structure updates in real-time


Enhancement Ideas

  1. Add persistence using localStorage

  2. Implement drag-and-drop reorganization

  3. Add undo/redo functionality

  4. Show traversal paths with animation

  5. Add context menu for operations

Here’s a deep dive into advanced DOM methods and techniques, including traversal, manipulation, and edge cases:


1. Advanced Element Traversal

Parent/Child Navigation

  • parentElement vs parentNode:

      const child = document.querySelector('.child');
      console.log(child.parentElement); // Returns parent element (ignores non-element nodes)
      console.log(child.parentNode);    // Returns any parent node (including text/comment nodes)
    
  • children vs childNodes:

      const parent = document.querySelector('.parent');
      console.log(parent.children);     // Live HTMLCollection of **element** children
      console.log(parent.childNodes);   // NodeList of **all** nodes (text, comments, elements)
    

Sibling Navigation

  • nextElementSibling vs nextSibling:

      const node = document.querySelector('.node');
      console.log(node.nextElementSibling); // Next element sibling (ignores text nodes)
      console.log(node.nextSibling);        // Next node (could be text/comment)
    
  • previousElementSibling:
    Works like nextElementSibling but in reverse direction.


2. Advanced Insertion Methods

insertAdjacentHTML()

Insert elements at specific positions relative to a node:

const refNode = document.querySelector('.reference');
refNode.insertAdjacentHTML('beforebegin', '<div>Before</div>'); // Outside, before
refNode.insertAdjacentHTML('afterbegin', '<div>First Child</div>'); // Inside, first
refNode.insertAdjacentHTML('beforeend', '<div>Last Child</div>');   // Inside, last
refNode.insertAdjacentHTML('afterend', '<div>After</div>');    // Outside, after

replaceWith() & replaceChild()

Replace elements:

const oldDiv = document.querySelector('.old');
const newDiv = document.createElement('div');

// Modern method:
oldDiv.replaceWith(newDiv);

// Legacy method:
oldDiv.parentNode.replaceChild(newDiv, oldDiv);

cloneNode()

Create copies of elements:

const original = document.querySelector('.template');
const deepCopy = original.cloneNode(true);  // Clone with children
const shallowCopy = original.cloneNode(false); // Clone without children

3. Attribute & Property Methods

hasAttribute() & removeAttribute()

Check for and delete attributes:

const link = document.querySelector('a');
if (link.hasAttribute('target')) {
  link.removeAttribute('target');
}

toggleAttribute()

Toggle boolean attributes:

const button = document.querySelector('button');
button.toggleAttribute('disabled'); // Adds if missing, removes if present

dataset Property

Access custom data-* attributes:

<div id="user" data-user-id="42" data-role="admin"></div>
const userDiv = document.getElementById('user');
console.log(userDiv.dataset.userId); // "42"
console.log(userDiv.dataset.role);   // "admin"

4. Advanced Content Manipulation

insertAdjacentText()

Safely insert text without parsing HTML:

const div = document.querySelector('.content');
div.insertAdjacentText('beforeend', 'New text <b>ignored</b>');

outerHTML Replacement

Replace an element including its own tags:

document.querySelector('.replace-me').outerHTML = '<span>New element</span>';

5. Advanced Styling

getComputedStyle()

Read final calculated styles:

const element = document.querySelector('.box');
const styles = window.getComputedStyle(element);
console.log(styles.backgroundColor); // "rgb(255, 0, 0)"

classList Advanced Methods

const div = document.querySelector('.container');
div.classList.replace('old-class', 'new-class'); // Replace class
console.log(div.classList.contains('active'));   // Check existence

6. Collection Handling

Convert Live Collections to Arrays

// For HTMLCollection:
const liveItems = document.getElementsByClassName('item');
const itemsArray = Array.from(liveItems);

// For NodeList (querySelectorAll):
const nodeList = document.querySelectorAll('.items');
const anotherArray = [...nodeList]; // Spread operator

Live vs Static Collections

// Live collection (updates automatically):
const live = document.getElementsByTagName('div');

// Static collection (snapshot):
const static = document.querySelectorAll('div');

7. Advanced Project Idea: Dynamic Table Manager

Features:

  • Add/remove rows and columns

  • Sort rows by clicking headers

  • Highlight parent/child cells

  • Undo/redo functionality

DOM Methods Used:

// Add row
function addRow() {
  const newRow = document.createElement('tr');
  newRow.innerHTML = '<td contenteditable></td>'.repeat(columnCount);
  table.appendChild(newRow);
}

// Sort rows
function sortRows(columnIndex) {
  const rows = Array.from(table.rows);
  rows.sort((a, b) => a.cells[columnIndex].textContent.localeCompare(b.cells[columnIndex].textContent));
  table.tBodies[0].append(...rows);
}

// Highlight hierarchy
table.addEventListener('click', (e) => {
  const cell = e.target.closest('td');
  if (cell) {
    // Highlight all children of parent row
    const parentRow = cell.parentElement;
    Array.from(parentRow.children).forEach(td => td.classList.toggle('highlight'));
  }
});

8. Key Differences Table

Method/PropertyUse Case
parentElementWhen you specifically need an element parent
parentNodeWhen you need any type of parent node (text/comment/element)
append()Modern method to add multiple nodes/text (supports multiple arguments)
appendChild()Legacy method for single node insertion
textContentSafe text manipulation (prevents XSS)
innerHTMLWhen intentional HTML parsing is needed
insertAdjacentHTML()Precise HTML insertion without destroying existing content

Best Practices

  1. Batch DOM Changes: Use DocumentFragment for multiple insertions:

     const fragment = new DocumentFragment();
     for (let i = 0; i < 100; i++) {
       const div = document.createElement('div');
       fragment.appendChild(div);
     }
     container.appendChild(fragment);
    
  2. Debounce Rapid Updates: For scroll/resize events, use:

     window.addEventListener('resize', debounce(handleResize, 200));
    
  3. Avoid Forced Synchronous Layouts:

     // Bad: Causes layout thrashing
     element.style.width = '100px';
     console.log(element.offsetWidth);
     element.style.height = '200px';
    
     // Good: Batch reads/writes
     element.style.width = '100px';
     element.style.height = '200px';
     console.log(element.offsetWidth);