Step 06

Creation

So far every node existed because the parser read it out of your markup. Now the script grows the tree itself. There is a long way and several shortcuts, and this step takes them in that order on purpose: the long way teaches you what a node actually is, and then each shortcut earns its place by solving a problem the long way makes obvious.

Learning Objectives

  • Build an element with createElement, fill it safely, and attach it with append
  • Batch many insertions into one live-tree touch with DocumentFragment
  • Stamp repeating structure from a <template> with cloneNode(true)
  • Place typed markup precisely with insertAdjacentHTML and its position keywords

The long way, first

Everything in the demo's board goes through one factory function, and it is worth reading slowly because there is no magic left in it:

function makeCard(title, body) { const card = document.createElement('article'); card.className = 'card'; const h3 = document.createElement('h3'); h3.textContent = title; const p = document.createElement('p'); p.textContent = body; const btns = document.createElement('div'); btns.className = 'actions'; const rm = document.createElement('button'); rm.type = 'button'; rm.dataset.action = 'remove'; rm.textContent = 'Remove'; btns.append(rm); card.append(h3, p, btns); return card; }

Create, configure, connect. Each createElement call makes a detached node — a real element that is simply not in the tree yet — and append wires the pieces together. Note where the user-facing strings go in: textContent, so the values are data, never markup. Verbose? Yes. But every shortcut below is sugar over exactly these moves, and when a shortcut misbehaves, this is the level you debug at.

Batching: one touch, not one hundred

Appending into the live tree makes the browser account for the change — do it in a loop and you pay per iteration. A DocumentFragment is a weightless off-tree container: fill it at leisure, insert it once, and the fragment dissolves, leaving its children behind.

const frag = document.createDocumentFragment(); for (let i = 1; i <= 100; i++) frag.append(makeCard('Batch ' + i, '…')); board.append(frag); // one insertion, one reflow — not one hundred

Templates: structure in markup, values in script

When the structure repeats, writing it in JavaScript buries markup in code. The <template> element flips that: inert HTML that the browser parses but neither renders nor runs, waiting to be cloned.

<template id="cardTemplate"> <article class="card" role="group" aria-label="Note"> <h3></h3> <p class="content"></p> <div class="actions"> <button type="button" data-action="remove">Remove</button> </div> </article> </template> const clone = t.content.firstElementChild.cloneNode(true); clone.querySelector('h3').textContent = $('#title').value; clone.querySelector('.content').textContent = $('#body').value; board.append(clone);

Structure lives where structure belongs, and script only fills in the blanks — through textContent, so user input stays inert. Notice too how the cards' Remove buttons work: one delegated listener on the board, using e.target.closest('button[data-action="remove"]'). That is event delegation paying off exactly as promised — cards created a moment ago are handled by a listener attached before they existed.

If you can type it, you can make it

Sometimes the honest shortcut is to write markup as a string. insertAdjacentHTML is the precise version of that idea: it parses a string and places the result at one of four positions relative to an element — beforebegin, afterbegin, beforeend, afterend.

board.insertAdjacentHTML('beforebegin', '<p class="log">Inserted before board</p>'); board.insertAdjacentHTML('afterend', '<p class="log">Inserted after board</p>');
Same parser, same rule. insertAdjacentHTML runs your string through the HTML parser just like innerHTML does — the injection warning from step 5 applies verbatim. Literal markup you typed: fine. Anything containing outside input: build it the long way or through a template instead.

Live demo

All three techniques against one card board: single cards the long way, a hundred at once through a fragment, template stamping with your own title and body, and insertAdjacent* placing paragraphs around the board itself.

Applied

Spoiler toggle. A disclosure widget from almost nothing: content starts with the hidden attribute, and one button flips hidden and mirrors the state into aria-expanded so assistive tech hears what sighted users see. Before building this yourself, ask whether native <details>/<summary> already does the job — the DOM version is for when you need control the native element doesn't give.

Roster builder. Three series' worth of APIs in thirty lines: a form submit event, FormData to read the fields, a template clone filled via textContent, and append onto the roster grid. This is the shape of most small DOM applications you will ever write.

Next Steps

In Step 7: Destruction, the tree shrinks:

  • Remove nodes with remove() and empty containers wholesale
  • See why deletion loops run bottom-up
  • Clean up listeners with AbortController so removed nodes don't leak