Step 05

Modification

Selection found the node; now change it. Modification is the most common DOM work there is — new text, a different link target, a flipped class, a swapped list item — and nearly all of it comes down to choosing the right one of a handful of tools. The choice matters more than it looks: one of these tools parses whatever you hand it as live markup.

Learning Objectives

  • Change content with textContent, and state precisely why it is the safe default
  • Explain the injection hazard innerHTML carries with untrusted input
  • Add, toggle, and remove attributes; write metadata through dataset
  • Swap a node with replaceWith and move one with prepend

Text or markup — decide on purpose

Both of these replace an element's contents. They are not interchangeable:

$('#textTarget').textContent = 'User <strong>text</strong> (rendered as text)'; $('#textTarget').innerHTML = 'Trusted <strong>HTML</strong> (rendered as HTML)';

textContent treats the string as text, tags and all — the <strong> above appears on screen as angle brackets. innerHTML hands the string to the HTML parser, which builds real nodes from it. That is exactly what you want for markup you wrote, and exactly what an attacker wants for markup they wrote.

innerHTML plus user input is a code-injection hole. If any part of the string came from outside your code — a form field, a URL, an API response — the parser will happily build whatever it says, script hooks included. This is the classic cross-site scripting (XSS) mistake, and the fix is the default you already have: put untrusted values in with textContent, always.

Attributes, classes, styles

The state-changing tools from step 4 are half of modification in practice. The demo exercises all of them against a single link and paragraph:

a.setAttribute('href', 'https://example.com'); if (a.hasAttribute('title')) a.removeAttribute('title'); else a.setAttribute('title', 'A helpful title'); a.dataset.id = '99'; $('#p').classList.toggle('fancy'); // the right tool for styling $('#p').style.color = '#0d6efd'; // the escape hatch, sparingly

The ranking from last step holds under modification: reach for the class first and let CSS carry the design; write style directly only for values computed at runtime — and even then, consider setting a CSS custom property instead, as the demo's third button does.

Structural surgery

Content and state changes leave the tree's shape alone. Two more calls change the shape itself:

const x = document.createElement('li'); x.textContent = 'X'; b.replaceWith(x); // B is out of the tree, X is in its place list.prepend(c); // move existing node to the front

The second line is the one that surprises people. There is no copy: a node lives in exactly one place in the tree, so inserting a node that is already attached moves it. Watch item C jump to the front of the list in the demo — one call, no clone, no removal step.

One node, one place. Every insertion method — append, prepend, before, after — relocates an already-attached node rather than duplicating it. When you really want a copy, that is what cloneNode is for, next step.

Live demo

All four families in one page: text versus HTML, attributes and dataset, classes and styles, and the structural moves. Every action reports to its own live-region log.

Applied: redacted text

A whole feature in one class flip. The stylesheet defines what redaction looks like — .redacted .blk paints marked spans black-on-black — and the script's entire job is classList.add('redacted') and classList.remove('redacted') on the container. This is the flip-a-class principle at full strength: design in CSS, one bit of state in JS.

Visual redaction is not data redaction. The “hidden” words are still in the document — black-on-black is a paint job, and screen readers, find-in-page, and copy-paste all see straight through it. If information must actually be withheld, it cannot be in the markup at all.

Next Steps

In Step 6: Creation, we stop editing what the parser built and start growing the tree ourselves:

  • Build elements the long way with createElement and textContent
  • Batch a hundred insertions into one with DocumentFragment
  • Stamp out repeating structure from a <template> with cloneNode
  • Type markup into place with insertAdjacentHTML