HTML, CSS & JavaScript

Chapter 11
DOM Manipulation and Event-Driven Programming

Certificate in Full stack programming
HTML, CSS & JavaScript   •   Certificate

DOM Manipulation and Event-Driven Programming

Introduction

A web page is not merely a document sent from a server. Once loaded by a browser, its HTML is represented as a live object structure that JavaScript can inspect and modify. This structure is the Document Object Model (DOM). Through the DOM, a program can change text, attributes, styles, structure, and accessibility information in response to user actions or other events.

Event-driven programming provides the operating model for interactive web applications. Instead of executing only from top to bottom, a program waits for events—such as clicks, keyboard input, form submission, page loading, or pointer movement—and runs an appropriate event handler. Effective DOM programming therefore requires both structural knowledge of the document and careful management of events, user input, accessibility, and security.

Learning Objectives

By the end of this chapter, you should be able to:

  • Explain the DOM as a tree of objects.
  • Select, traverse, create, update, and remove DOM nodes.
  • Distinguish properties, attributes, classes, and inline styles.
  • Register event handlers and explain event propagation.
  • Use event delegation appropriately.
  • Process forms and perform accessible client-side validation.
  • Avoid common performance, security, and accessibility problems.

1. The Document Object Model

When a browser parses HTML, it creates a tree. The document is the root; beneath it are elements such as html, head, and body. Elements may contain text nodes, comments, and other elements. JavaScript accesses this tree through the global document object.

Consider:

<main id="content">
  <h1>Courses</h1>
  <p class="intro">Choose a course.</p>
</main>

The main element is an element node. Its heading and paragraph are child nodes. The text inside the heading is a text node. This distinction matters because children returns element children, whereas childNodes may also include whitespace text nodes.

The DOM is a programming interface, not the original HTML text. Changes made through JavaScript affect the current document displayed by the browser, but they do not automatically modify the file stored on the server.

2. Selecting and Traversing Elements

2.1 Selecting elements

Common selection methods include:

MethodResultTypical use
getElementById(id)One element or nullKnown unique identifier
querySelector(selector)First matching element or nullCSS selector selection
querySelectorAll(selector)Static NodeListAll matching elements
getElementsByClassName(name)Live collectionLegacy or collection-based code
getElementsByTagName(name)Live collectionElements of a tag name

querySelector and querySelectorAll accept CSS selectors:

const title = document.querySelector('h1');
const cards = document.querySelectorAll('.course-card');
const submit = document.querySelector('form button[type="submit"]');

A selector may return no result. Code should therefore account for null before using a single selected element:

const banner = document.querySelector('#banner');
if (banner) {
  banner.textContent = 'Welcome';
}

A NodeList returned by querySelectorAll is iterable and can be processed with forEach:

document.querySelectorAll('.course-card').forEach(card => {
  card.classList.add('visible');
});

2.2 Traversing the tree

Useful properties include:

  • parentElement: the containing element.
  • children: element children only.
  • firstElementChild and lastElementChild.
  • nextElementSibling and previousElementSibling.
  • closest(selector): the nearest ancestor, including the element itself, matching a selector.
  • matches(selector): tests whether an element matches a selector.

For example:

const item = document.querySelector('.course-item');
const list = item.closest('ul');
const next = item.nextElementSibling;

Prefer element-based properties when whitespace text nodes should not be included. Traversal is especially useful when an event begins on a deeply nested child but the application needs the containing component.

3. Reading and Updating Content

3.1 Text content and HTML content

textContent reads or writes plain text:

message.textContent = 'Registration complete.';

It treats input as text and does not interpret markup. innerHTML reads or writes HTML markup:

panel.innerHTML = '<strong>Registration complete.</strong>';

Because innerHTML parses a string as HTML, inserting untrusted user input through it can create a cross-site scripting (XSS) vulnerability. Use textContent for user-provided text whenever possible. If HTML is genuinely required, use a trusted, carefully controlled source or an approved sanitization process.

innerText reflects rendered text and can be affected by CSS and layout. For ordinary DOM text manipulation, textContent is generally more predictable and efficient.

3.2 Attributes and properties

Attributes are values written in HTML, such as id, href, disabled, and aria-label. DOM properties are JavaScript representations of element state. For example, a checkbox has the HTML attribute checked, but its current state is normally read through the property checkbox.checked.

const link = document.querySelector('a');
link.setAttribute('aria-label', 'Read course details');

const checkbox = document.querySelector('#terms');
console.log(checkbox.checked);

Useful methods include getAttribute, setAttribute, hasAttribute, and removeAttribute. Boolean attributes such as disabled are true when present, although their JavaScript property should normally be used to inspect current state.

3.3 Classes and styles

Use classList for reusable visual states:

card.classList.add('selected');
card.classList.remove('hidden');
card.classList.toggle('expanded');
const active = card.classList.contains('active');

This is preferable to repeatedly assigning the entire className, which may accidentally remove classes added by other code.

Inline styles can be changed through the style property:

box.style.backgroundColor = 'lavender';
box.style.setProperty('--accent-color', 'teal');

For maintainable applications, CSS classes are usually preferable because presentation remains in CSS and state remains explicit in the DOM. getComputedStyle(element) reads the final style after stylesheets and inheritance have been applied; it does not change the element.

4. Creating, Inserting, and Removing Nodes

The standard safe construction pattern is:

const li = document.createElement('li');
li.textContent = 'Web accessibility';
li.classList.add('topic');

const list = document.querySelector('#topics');
list.append(li);

Other insertion methods include prepend, before, after, and replaceWith. A node can be moved by inserting it elsewhere; it is not copied automatically.

const heading = document.createElement('h2');
heading.textContent = 'Current modules';
list.before(heading);

To remove an element:

li.remove();

cloneNode(true) creates a copy including descendants, but event listeners registered directly on the original node are not copied. If a large amount of HTML must be inserted, a DocumentFragment can reduce repeated rendering work:

const fragment = document.createDocumentFragment();
for (const name of ['HTML', 'CSS', 'JavaScript']) {
  const li = document.createElement('li');
  li.textContent = name;
  fragment.append(li);
}
document.querySelector('#topics').append(fragment);

5. Event-Driven Programming

An event represents something that happens in the browser. Examples include click, input, change, submit, keydown, focus, and DOMContentLoaded. An event listener associates an event type with a function.

const button = document.querySelector('#save');
button.addEventListener('click', event => {
  event.preventDefault();
  console.log('Saved');
});

addEventListener is preferred to inline HTML handlers such as onclick because it separates behavior from structure and permits multiple listeners. The event object provides information such as target, currentTarget, keyboard data, pointer coordinates, and modifier keys.

  • event.target is the original dispatch target.
  • event.currentTarget is the element whose listener is currently running.
  • event.preventDefault() cancels a browser default action when cancellation is allowed.
  • event.stopPropagation() stops further movement through the propagation path; it should be used sparingly.

A listener can be removed only when the same function reference is supplied:

function report() {
  console.log('reported');
}
button.addEventListener('click', report);
button.removeEventListener('click', report);

6. Event Propagation

Most DOM events move through three conceptual phases:

  1. Capturing phase: the event travels from the document toward the target.
  2. Target phase: the event reaches the original target.
  3. Bubbling phase: the event travels back toward the document.

A listener can be registered for capturing with { capture: true }, but ordinary listeners use bubbling. The propagation model explains why a click on a button can also trigger a listener on its containing card.

card.addEventListener('click', event => {
  console.log('card received the bubbling event');
});

stopPropagation() prevents the event from reaching other nodes, while stopImmediatePropagation() also prevents later listeners on the same node. These methods can make components difficult to compose, so event logic should normally be designed without relying on them.

7. Event Delegation

Event delegation places one listener on a stable ancestor and uses bubbling to handle events from descendants. It is efficient for lists with many items and automatically supports items added later.

const list = document.querySelector('#topics');
list.addEventListener('click', event => {
  const button = event.target.closest('button[data-topic]');
  if (!button || !list.contains(button)) return;
  console.log(button.dataset.topic);
});

The data-* attribute is exposed through element.dataset. Delegation is not suitable for every event: some events do not bubble, and highly complex components may need direct listeners. Keyboard and pointer behavior must also be considered; a clickable div is not automatically an accessible button. Prefer semantic controls such as <button>.

8. Forms and Client-Side Validation

The submit event belongs to the form, not merely to its submit button. A handler can prevent an invalid submission and display useful feedback:

const form = document.querySelector('#enrolment');
const email = document.querySelector('#email');
const error = document.querySelector('#email-error');

form.addEventListener('submit', event => {
  error.textContent = '';
  email.removeAttribute('aria-invalid');

  if (!email.validity.valid) {
    event.preventDefault();
    error.textContent = 'Enter a valid email address.';
    email.setAttribute('aria-invalid', 'true');
    email.focus();
  }
});

HTML constraints such as required, type="email", minlength, maxlength, min, and pattern provide a first layer of validation. The Constraint Validation API includes validity.valid, checkValidity(), reportValidity(), and setCustomValidity().

Client-side validation improves usability but is not a security boundary. A user can disable JavaScript or alter requests. The server must validate all submitted data independently, authorize actions, and safely process stored values.

Accessible validation

Validation should:

  • associate a visible label with every control;
  • explain the problem in text, not only colour;
  • preserve entered values where possible;
  • identify invalid controls with aria-invalid="true";
  • connect messages using aria-describedby;
  • move focus thoughtfully, usually to the first invalid control;
  • avoid unexpected focus changes while a user is typing.

An error container may use role="alert" when immediate announcement is appropriate, but excessive live-region announcements can be distracting. Native controls and browser validation should be enhanced rather than unnecessarily replaced.

9. Applied Case: Accessible Course Registration Form

Background

An illustrative training provider offers a course-registration form containing a name, email address, selected course, and terms checkbox. The original version displays a red border for invalid fields but gives no text explanation and submits even when the terms box is unchecked.

Problem

The interface must provide immediate feedback, remain usable with keyboard and screen-reader technology, and prevent invalid client-side submission without treating browser code as a security control.

Approach

The form uses semantic labels, native input types, required constraints, and dedicated error elements. One submit listener checks validity. The first invalid field receives focus. Error text is inserted with textContent, and the invalid field references its message through aria-describedby.

<label for="email">Email address</label>
<input id="email" name="email" type="email"
       required aria-describedby="email-error">
<p id="email-error"></p>
form.addEventListener('submit', event => {
  const fields = [...form.elements].filter(control =>
    control.matches('input, select, textarea'));

  for (const field of fields) {
    field.removeAttribute('aria-invalid');
  }

  if (!form.checkValidity()) {
    event.preventDefault();
    const firstInvalid = fields.find(field => !field.validity.valid);
    if (firstInvalid) {
      firstInvalid.setAttribute('aria-invalid', 'true');
      firstInvalid.focus();
    }
  }
});

Outcome and learning points

The form now uses the browser’s semantic validation model, gives users a clear path to correction, and supports keyboard navigation. The server still repeats validation. The case demonstrates that robust DOM programming combines functionality, accessibility, security, and maintainable separation between HTML, CSS, and JavaScript.

10. Performance, Security, and Maintainability

Repeated layout reads and writes can cause unnecessary rendering work. For example, alternating measurement and style changes in a large loop may force repeated layout calculations. Prefer batching DOM updates, using classes, and applying a DocumentFragment where appropriate. Do not optimize prematurely; measure actual bottlenecks with browser developer tools.

Never assume DOM content is trusted. Avoid inserting unsanitized strings with innerHTML, validate URLs before assigning them, and use secure server-side processing. DOM-based XSS can occur even when data never reaches a server.

Use meaningful names, small event handlers, and functions with one clear responsibility. Remove listeners associated with discarded components when necessary. Avoid global variables and avoid storing duplicated state when the DOM or application model can provide a single source of truth.

11. Contemporary Practice

Modern applications may use component frameworks, but the underlying principles remain DOM selection, state change, event handling, and accessibility. Framework abstractions do not eliminate browser event propagation or security responsibilities. The platform also provides useful modern features such as AbortController for cancelling listeners, Element.closest() for delegation, and requestAnimationFrame() for visual updates. These features should be adopted when they solve a real design problem rather than merely increase complexity.

Chapter Summary

The DOM represents a loaded HTML document as a programmable tree. JavaScript can select and traverse nodes, update text and attributes, manage classes, create elements, and remove content. textContent is safer than unsanitized innerHTML for user data. Events support interactive behavior through listeners, event objects, propagation phases, and delegation. Forms should use semantic HTML and the Constraint Validation API, while client-side checks must be supplemented by server-side validation. Accessible feedback requires labels, visible explanations, appropriate ARIA relationships, and thoughtful focus management. Professional DOM programming balances correctness, usability, security, performance, and maintainability.

Key Terms

DOM: Object representation of a document used by scripts.

Node: An item in the DOM tree, such as an element or text node.

Element: A DOM node representing an HTML element.

Selector: A CSS-pattern expression used to locate elements.

Traversal: Moving between related nodes in the DOM tree.

textContent: Property for reading or writing plain text.

innerHTML: Property for reading or parsing HTML markup.

Attribute: Name–value information written on an HTML element.

Property: JavaScript-accessible state of a DOM object.

classList: Interface for managing CSS classes.

Event: A browser-notified occurrence such as a click or submission.

Event listener: Function registered to respond to an event.

Event target: Original object on which an event occurred.

Event propagation: Movement of an event through the DOM.

Event delegation: Handling descendant events through an ancestor listener.

Constraint Validation API: Browser API for checking form constraints.

preventDefault(): Method that cancels a cancelable default action.

XSS: Injection of executable untrusted content into a web context.

Accessibility: Design enabling people with diverse abilities to use software.

Review Questions

Short Answer Questions

  1. What is the DOM, and how does it relate to an HTML document?
  2. Why is textContent usually safer than assigning untrusted input to innerHTML?
  3. Distinguish event.target from event.currentTarget.
  4. What is event delegation, and when is it useful?
  5. Why can client-side validation not replace server-side validation?

Long Answer Questions

  1. Explain the principal methods for selecting and traversing DOM elements, including the difference between children and childNodes.
  2. Describe event capturing, targeting, and bubbling, and explain the roles of preventDefault() and stopPropagation().
  3. Explain how classes, attributes, properties, and inline styles should be used in a maintainable interface.
  4. Develop an accessible form-validation strategy using native HTML constraints and the Constraint Validation API.
  5. Discuss the security, performance, and maintainability risks of careless DOM manipulation.

Analytical / Application Questions

  1. A dynamically generated list contains 2,000 buttons. Design an event-handling strategy that supports both existing and future buttons, and justify your choice.
  2. A registration form shows only red borders when fields are invalid. Analyse the accessibility problems and propose specific improvements.
  3. An application inserts a user’s biography with innerHTML. Evaluate the security risk and describe a safer implementation for plain text and for intentionally permitted markup.

Multiple Choice Questions with Answers

Question 1

Which method returns the first element matching a CSS selector?

A. querySelector()

B. querySelectorAll()

C. getElementsByClassName()

D. createElement()

Correct Answer: A. querySelector()

Question 2

Which property is most appropriate for inserting untrusted user input as plain text?

A. innerHTML

B. textContent

C. outerHTML

D. style

Correct Answer: B. textContent

Question 3

What does event.target identify?

A. The element whose listener is currently executing

B. The document root

C. The original object on which the event occurred

D. The nearest form element

Correct Answer: C. The original object on which the event occurred

Question 4

What is the main advantage of event delegation?

A. It prevents all events from bubbling

B. It allows an ancestor to handle events from current and future descendants

C. It automatically validates forms

D. It converts div elements into buttons

Correct Answer: B. It allows an ancestor to handle events from current and future descendants

Question 5

Which method cancels a form’s normal submission when the event is cancelable?

A. stopPropagation()

B. remove()

C. preventDefault()

D. cancelEventListener()

Correct Answer: C. preventDefault()

Question 6

Which property represents the current checked state of a checkbox?

A. checked

B. selectedAttribute

C. isCheckedAttribute

D. checkStateText

Correct Answer: A. checked

Question 7

Which practice best supports accessible validation feedback?

A. Use colour alone to identify errors

B. Remove the user’s input after every error

C. Provide text explanations and associate them with controls

D. Disable keyboard focus on invalid controls

Correct Answer: C. Provide text explanations and associate them with controls

Question 8

Which API method checks whether a form currently satisfies its constraints without necessarily submitting it?

A. checkValidity()

B. submitValidity()

C. validateHTML()

D. testForm()

Correct Answer: A. checkValidity()

Question 9

Why can unsanitized user input assigned through innerHTML be dangerous?

A. It always removes CSS classes

B. It may be interpreted as executable markup or script

C. It prevents event bubbling

D. It converts text into a server request

Correct Answer: B. It may be interpreted as executable markup or script

Question 10

Which operation creates a new element node?

A. document.createElement()

B. document.newNodeText()

C. element.makeChild()

D. document.appendElement()

Correct Answer: A. document.createElement()

HTML, CSS & JavaScript
Chapter 11 of 15