Home Module 10 Modifying Elements

1. Introduction

Once you can select elements, the next skill is modifying them. This is where pages become truly dynamic: updating text when data loads, toggling a class to apply a CSS style, changing an image source, or updating a button's disabled state.

This lesson covers five categories of modification:

  1. Content — textContent, innerHTML
  2. Attributes — getAttribute, setAttribute, removeAttribute, dataset
  3. Classes — classList.add/remove/toggle/contains
  4. Inline styles — style property
  5. Form values — value, checked, disabled

2. Theory

2.1 Changing text content

const heading = document.querySelector('h1');

// textContent — plain text (safe, no HTML parsing)
heading.textContent = 'New Heading';
console.log(heading.textContent); // 'New Heading'

// innerText — similar to textContent but respects CSS visibility
// (textContent is usually preferred — faster and more predictable)

// innerHTML — parses as HTML (use carefully!)
heading.innerHTML = 'Hello <em>World</em>';
// The heading now renders with italic "World"
Security warning: Never set innerHTML to user-supplied content without sanitising. It can execute malicious scripts (XSS attack). Use textContent for user data.

2.2 Attributes — getAttribute, setAttribute, removeAttribute

const img  = document.querySelector('img');
const link = document.querySelector('a');
const btn  = document.querySelector('button');

// Read an attribute
img.getAttribute('src');    // '/images/cat.jpg'
link.getAttribute('href');  // 'https://...'

// Set an attribute
img.setAttribute('src', '/images/dog.jpg');
img.setAttribute('alt', 'A dog playing');
link.setAttribute('href', 'https://example.com');

// Remove an attribute
btn.removeAttribute('disabled');

// Check if attribute exists
img.hasAttribute('alt'); // true or false

Direct property access (usually preferred)

Many attributes are directly accessible as element properties — cleaner than getAttribute/setAttribute:

img.src  = '/images/dog.jpg';   // same as setAttribute('src', ...)
img.alt  = 'A dog';
link.href = 'https://example.com';
input.placeholder = 'Enter name...';
btn.disabled = true;  // disables the button
btn.disabled = false; // enables it

2.3 data-* attributes (dataset)

Custom HTML attributes starting with data- are accessible through the dataset property:

<!-- HTML -->
<button data-id="42" data-action="delete">Delete</button>
const btn = document.querySelector('button');

// Read
btn.dataset.id;      // '42' (always a string)
btn.dataset.action;  // 'delete'

// Write
btn.dataset.id = '99';
btn.dataset.newProp = 'hello'; // adds data-new-prop="hello"

// Note: camelCase in JS maps to kebab-case in HTML
// dataset.userId → data-user-id in HTML

2.4 classList — working with CSS classes

The preferred way to manage an element's classes. Never overwrite className directly — that destroys existing classes.

const card = document.querySelector('.card');

card.classList.add('featured');       // adds 'featured' class
card.classList.remove('hidden');      // removes 'hidden' class
card.classList.toggle('active');      // adds if absent, removes if present
card.classList.contains('featured');  // true or false
card.classList.replace('old', 'new'); // replace one class with another

// Add multiple at once
card.classList.add('highlight', 'bold');

// Read all classes
console.log(card.className); // 'card featured highlight bold'
console.log([...card.classList]); // ['card', 'featured', 'highlight', 'bold']

2.5 Inline styles

Set CSS directly on an element using the style property. CSS property names become camelCase:

const box = document.querySelector('.box');

box.style.backgroundColor = 'royalblue'; // background-color
box.style.fontSize         = '1.5rem';   // font-size
box.style.borderRadius     = '8px';
box.style.display          = 'none';     // hide
box.style.display          = '';         // restore (empty string removes inline style)

// Read computed (final) styles (after CSS is applied)
const styles = getComputedStyle(box);
console.log(styles.fontSize);       // '24px' (resolved value)
console.log(styles.backgroundColor); // 'rgb(65, 105, 225)'

Prefer toggling CSS classes over setting inline styles — it keeps styling in CSS where it belongs.

2.6 Form element properties

const input    = document.querySelector('#username');
const checkbox = document.querySelector('#agree');
const select   = document.querySelector('#country');
const textarea = document.querySelector('#message');

// Read values
input.value;          // text typed by user
checkbox.checked;     // true or false
select.value;         // value of selected option
textarea.value;       // multi-line text

// Set values
input.value = 'Alice';
checkbox.checked = true;
select.value = 'CA'; // selects the option with value="CA"

// Disable/enable
input.disabled = true;
input.disabled = false;

// Focus
input.focus();
input.blur();
input.select(); // select all text in the input

2.7 Reading vs writing

Most DOM properties work in both directions:

// Read
const text = heading.textContent;

// Write
heading.textContent = 'New text';

// Read
const src = img.src;

// Write
img.src = 'new-image.jpg';

3. Real World Example

// Dark mode toggle
const toggle = document.querySelector('#theme-toggle');
const html   = document.documentElement; // <html> element

toggle.addEventListener('click', () => {
  html.classList.toggle('dark');
  toggle.textContent = html.classList.contains('dark') ? 'Light Mode' : 'Dark Mode';
  localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
});

// Restore on page load
if (localStorage.getItem('theme') === 'dark') {
  html.classList.add('dark');
  toggle.textContent = 'Light Mode';
}

// Form character counter
const bio   = document.querySelector('#bio');
const count = document.querySelector('#char-count');
const MAX   = 160;

bio.addEventListener('input', () => {
  const remaining = MAX - bio.value.length;
  count.textContent = remaining + ' characters remaining';
  count.style.color = remaining < 20 ? 'crimson' : '';
});

4. Code Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Modifying Elements Demo</title>
  <style>
    .card { border: 2px solid #ddd; padding: 1rem; border-radius: 8px; max-width: 300px; }
    .highlighted { border-color: royalblue; background: #eff6ff; }
    .hidden { display: none; }
  </style>
</head>
<body>
  <div class="card" id="profile-card" data-user-id="42">
    <img id="avatar" src="avatar-placeholder.png" alt="User avatar" width="60">
    <h2 id="user-name">Loading...</h2>
    <p id="user-bio"></p>
    <button id="follow-btn">Follow</button>
  </div>

  <button id="load-btn">Load User</button>
  <button id="toggle-btn">Toggle Highlight</button>

  <script>
    const card      = document.querySelector('#profile-card');
    const nameEl    = document.querySelector('#user-name');
    const bioEl     = document.querySelector('#user-bio');
    const avatarEl  = document.querySelector('#avatar');
    const followBtn = document.querySelector('#follow-btn');

    // Simulate loading user data
    document.querySelector('#load-btn').addEventListener('click', () => {
      // 1. textContent — safe for user-generated text
      nameEl.textContent = 'Alice Johnson';

      // 2. innerHTML — safe here (controlled content)
      bioEl.innerHTML = 'Frontend developer. Loves <em>CSS Grid</em> and coffee.';

      // 3. Attributes
      avatarEl.src = 'https://i.pravatar.cc/60?img=1';
      avatarEl.alt = 'Alice Johnson avatar';

      // 4. dataset
      console.log('User ID:', card.dataset.userId); // '42'
    });

    // 5. classList toggle
    document.querySelector('#toggle-btn').addEventListener('click', () => {
      card.classList.toggle('highlighted');
    });

    // 6. Toggle follow button
    let following = false;
    followBtn.addEventListener('click', () => {
      following = !following;
      followBtn.textContent     = following ? 'Unfollow' : 'Follow';
      followBtn.style.background = following ? '#e63946' : '';
      followBtn.style.color      = following ? 'white'   : '';
    });
  </script>
</body>
</html>

5. Code Breakdown

textContent vs innerHTML choice

nameEl.textContent = 'Alice Johnson' — user name from an API should use textContent to prevent XSS if the name ever contains HTML characters. bioEl.innerHTML = '...' is used here because the content is controlled by the developer (not user-supplied) and needs to render an <em> tag.

avatarEl.src = ...

Directly setting the src property is equivalent to setAttribute('src', ...) but shorter. The browser immediately starts downloading the new image.

card.dataset.userId

Reads the data-user-id="42" attribute as a string. The HTML attribute data-user-id maps to dataset.userId (kebab-case → camelCase).

classList.toggle('highlighted')

Adds the class if absent, removes it if present — one line replaces an if/else. The CSS defines what "highlighted" looks like; JS just switches the class on and off.

followBtn.style.background = ''

Setting a style property to an empty string removes that inline style, reverting to the stylesheet value. This is the correct way to "undo" a style set by JavaScript.

6. Common Mistakes

Mistake 1 — Setting innerHTML from user input (XSS)

// DANGEROUS — user types '<img src=x onerror=alert(1)>'
el.innerHTML = userInput; // executes the onerror script!

// Safe — treats everything as plain text
el.textContent = userInput;

Mistake 2 — Overwriting className instead of using classList

// Bad — removes ALL existing classes
el.className = 'active'; // 'card featured' → 'active'

// Good — just adds the new class
el.classList.add('active'); // 'card featured' → 'card featured active'

Mistake 3 — CSS property names in camelCase confusion

// Bad — hyphenated names don't work
el.style.background-color = 'red'; // SyntaxError

// Good — camelCase
el.style.backgroundColor = 'red';

Mistake 4 — Reading style before element is rendered

// style.X only returns values SET inline, not CSS stylesheet values
el.style.fontSize; // '' if no inline style, even if CSS sets it to 20px

// getComputedStyle() reads the final resolved value
getComputedStyle(el).fontSize; // '20px'

Mistake 5 — dataset values are always strings

btn.dataset.count = '5';
btn.dataset.count + 1; // '51' — string concatenation!

// Fix: convert first
Number(btn.dataset.count) + 1; // 6

7. Best Practices

  1. Use textContent by default — switch to innerHTML only when you need to render HTML markup.
  2. Never innerHTML user data without sanitising (use DOMPurify or a framework).
  3. Toggle classes, not inline styles — keeps styling logic in CSS where it belongs.
  4. Use classList.toggle/add/remove — never overwrite className.
  5. Use direct property access (img.src) for common attributes — cleaner than getAttribute/setAttribute.
  6. Use dataset for custom data attributes — cleaner than arbitrary custom attributes.
  7. Remember dataset values are strings — convert numbers and booleans when reading.
  8. Set style.property = '' (empty string) to remove an inline style, not delete.

8. Practice Exercise

  1. Create a page with a paragraph, an image, a link, and a button.
  2. Add JavaScript that does the following when buttons are clicked:
    • "Change Text" — updates the paragraph text content
    • "Change Image" — updates the image src and alt
    • "Toggle Class" — toggles a 'highlighted' class on the paragraph
    • "Disable Button" — sets the button's disabled property to true
  3. Build a "Profile Editor": name input + bio textarea + "Save" button. On save, update display elements below the form with the entered values. Use textContent for the name and innerHTML (after basic sanitising) for the bio.

9. Assignment

Build a "Product Card Editor."

  1. Create a product card with: image, title, price, description, and a "Sale" badge (hidden by default).
  2. Build a form with inputs for: title, price, description, image URL, and a "On Sale?" checkbox.
  3. On form submit, update the card:
    • Set the card title using textContent
    • Set the price (format as "$X.XX")
    • Set the description
    • Update the image src and alt
    • Show/hide the sale badge using classList.toggle
  4. The "Sale" badge should also reduce the displayed price by 20% when checked.
  5. Add a "Reset" button that restores original values.

Deliverable: One HTML file.

10. Interview Questions

  1. What is the difference between textContent and innerHTML?
    textContent sets/gets plain text — HTML tags are treated as literals. innerHTML parses the string as HTML markup. Use textContent for user-generated content to prevent XSS.
  2. How do you add a CSS class to an element without removing its existing classes?
    Use element.classList.add('new-class'). Never use element.className = 'new-class' — that replaces all existing classes.
  3. What does classList.toggle() do?
    It adds the class if it is absent, removes it if it is present. A one-liner replacement for an if/else check. Optionally accepts a second boolean argument to force add (true) or remove (false).
  4. How do you read a CSS value that was set in a stylesheet (not inline)?
    Use getComputedStyle(element).propertyName. element.style.property only returns inline styles; it returns empty string for stylesheet-applied styles.
  5. What is the dataset property?
    A DOMStringMap of all data-* attributes on an element. data-user-id becomes dataset.userId. Values are always strings — convert numbers and booleans when reading.
  6. How do you remove an inline style set by JavaScript?
    Set the property to an empty string: element.style.backgroundColor = ''. This removes the inline style and allows the stylesheet value to apply again.

11. Additional Resources

  • MDN — Element.classList
  • MDN — HTMLElement.dataset
  • MDN — HTMLElement.style
  • MDN — Window.getComputedStyle()
  • javascript.info — Modifying the document
  • OWASP — Cross Site Scripting (XSS) — understand the innerHTML risk