Tree
Identify elements, text nodes and relationships.
CodeBhavyaInspect the document tree and make targeted, understandable page updates.
Level 15 transformed a workshop collection as data. This lesson connects that data to the browser’s document tree. You will select elements, read properties, change text, create and remove nodes, apply classes and inspect the current result.
Our task is a local workshop-card editor. Its controls update a small preview region without replacing the site header, sidebar or footer. You will distinguish a source HTML file from the live DOM and explain why a page update disappears after a reload unless a deliberate persistence mechanism exists.
Identify elements, text nodes and relationships.
Choose selectors and handle a missing match.
Use targeted text, property and node operations.
Keep labels, status and focus meaningful after changes.
The Document Object Model represents the document as objects that scripts can inspect and change. The browser parses HTML into a tree, and scripts can then change that live tree. The current DOM can differ from the text originally downloaded in the HTML file.
The document contains elements, text and other node kinds. An article is an element node; the words inside its heading belong to a text node. Whitespace between elements can also be a text node, which matters when traversing all child nodes.
A DOM update does not rewrite your GitHub file. Changing a title with textContent affects this document instance. Reloading normally starts again from the delivered page and its initialization code.
The loaded page document.
The collection container.
One workshop card.
Its title and explanatory text.
Document is the main entry point for the page’s document tree. Window exposes the browser window’s environment, including facilities such as timers and viewport information. These are browser APIs, not keywords built into the JavaScript language.
A pure function can calculate a record without document. A rendering function needs the document or a node to update. Keeping those responsibilities separate makes it easier to inspect a calculation without confusing it with layout behavior.
The DOM tree and the rendered layout are related but different. An element can exist in the DOM while being hidden from rendering. Counting element nodes does not prove the content is readable or operable.
const preview = document.getElementById("preview");
console.log(window.innerWidth);
console.log(preview !== null);Window.innerWidth is viewport information, not the width of every content panel. Use the appropriate element measurement when the question concerns a card or container.
A selector can return no match because the element is absent or because the script ran before parsing reached it. An external classic script with defer can run after parsing. DOMContentLoaded is another useful initialization point when you need the parsed document.
For a script that might be loaded after DOMContentLoaded has already fired, checking document.readyState avoids registering a listener for an event that will not happen again. Keep initialization idempotent if the application can call it more than once, so listeners are not duplicated.
This course’s lesson scripts are included in the page and initialize once on DOMContentLoaded. A component loader with a different lifecycle would need to account for its own insertion and cleanup.
function initPreview() {
const preview = document.getElementById("preview");
if (!preview) return;
// Set up this preview once.
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initPreview, { once: true });
} else {
initPreview();
}GetElementById looks up an ID string and returns the matching element or null. It does not take a CSS # prefix. QuerySelector takes a CSS selector and returns the first matching element or null. A malformed selector can throw a SyntaxError rather than merely return no match.
IDs should be unique within the document. A first-match selector is not a repair for duplicate IDs. Scope a query to the preview element when the action should affect its cards rather than unrelated cards in the lesson or sidebar.
Do not concatenate arbitrary user input into a selector and assume the resulting selector is valid. Generated IDs and controlled selectors simplify this lab. For an actual arbitrary identifier in a selector, use appropriate escaping such as CSS.escape.
const preview = document.getElementById("preview");
const firstTitle = preview?.querySelector(".card-title");
if (firstTitle) {
firstTitle.textContent = "HTML Workshop";
}Optional chaining avoids a property access on null here. It does not prove a required preview was successfully loaded; a real initialization failure should still be diagnosed rather than silently ignored everywhere.
QuerySelectorAll returns a static NodeList of matching elements. Its membership is a snapshot from the query time. If another card is inserted later, query again to obtain a list that includes it. The referenced elements themselves can still change; static does not mean frozen nodes.
Some other collections are live, such as an element’s children HTMLCollection. Its membership changes as the DOM changes. Iterating a live collection while removing elements can skip items if the indexes shift.
A NodeList is not an ordinary array with every array method. You can iterate it or convert it with Array.from when you want array operations. Choose a fresh query for an inspector that should reflect the current preview.
const before = preview.querySelectorAll(".card");
// Insert another card here.
const after = preview.querySelectorAll(".card");
console.log(before.length, after.length);
const titles = Array.from(after, card =>
card.querySelector(".card-title").textContent
);The lab’s inspector reads the current preview after each operation. It does not reuse the initialization query and then pretend that it counts newly added cards.
TextContent reads or sets textual content. Setting it on an element replaces that element’s children with text, so choose the intended leaf element. Updating the title heading preserves the rest of the article; setting the whole article’s textContent would remove its note and badge.
InnerHTML parses markup. It is useful in carefully controlled situations, but placing untrusted text into it can create an injection risk. This lab uses createElement and textContent so a supplied label such as <strong>HTML</strong> remains literal text.
TextContent is not a measurement of what is visually readable. It can include text from hidden descendants. InnerText has rendering-aware behavior and different costs; choose the property that matches the task.
const title = card.querySelector(".card-title");
title.textContent = userSuppliedTitle;
// Literal text, not parsed HTML markup.The preview editor changes a title and note separately. Its inspectable snapshot shows the same literal text instead of interpreting angle brackets as an instruction to build new elements.
An HTML attribute is part of the element’s attribute representation. A DOM property can expose current state. Some properties reflect attributes closely; others have distinct current and default states. Input.value is a common example: typing changes the current value without necessarily changing the value attribute.
A boolean attribute such as disabled depends on its presence. Disabled="false" still supplies the disabled attribute. Use button.disabled = false or removeAttribute("disabled") when you intend to enable the button.
Set attributes such as aria-pressed to the strings "true" or "false" according to the actual toggle state. Adding ARIA does not create the behavior it describes; the handler still has to perform the action.
button.disabled = cards.length === 0;
button.setAttribute("aria-pressed", highlighted ? "true" : "false");
console.log(input.value); // current user entry
console.log(input.getAttribute("value")); // attribute representationIn this lab, edit/remove/highlight controls are disabled when no card exists. The highlight toggle’s aria-pressed state is synchronized with the selected card’s class.
Data attributes can associate a small identifier with an element. Data-card-id is exposed as element.dataset.cardId. Dataset values are strings; storing data-count="3" does not make dataset.count a Number automatically.
Use a stable generated ID to match a select option with a preview card. A display title may change, contain spaces or be duplicated, so it is a poor identity key. Do not put private secrets in data attributes; they are visible in the client document.
A dataset is not a database or proof of trustworthy input. The lab generates its own card IDs and restricts actions to the preview’s current cards.
card.dataset.cardId = "card-1";
console.log(card.dataset.cardId); // "card-1"
// HTML representation: data-card-id="card-1"When a title is edited, the card keeps its ID. Adding two cards with the same title is allowed; their generated IDs remain different.
ClassList.add, remove and toggle update class tokens without replacing every existing class. A two-argument toggle can apply a boolean state deterministically. This is useful for a highlighted card whose appearance is already defined in CSS.
Element.style exposes inline declarations. It does not report every declaration from stylesheets. GetComputedStyle reads resolved style information for an element; its result should not be confused with the original stylesheet text.
A meaningful state needs more than color. The lab’s highlight state changes the border and adds a visible “Highlighted” badge while updating the toggle’s pressed state. Selection and highlighting are separate concepts.
card.classList.toggle("is-highlighted", highlighted);
badge.hidden = !highlighted;
// CSS controls the actual border and badge presentation.
const resolvedColor = getComputedStyle(card).color;Avoid continuously replacing style strings for every small state change. A scoped class is easier to review and does not accidentally remove unrelated inline properties.
CreateElement makes an element that is initially disconnected from the document. Set its text and attributes, assemble its children, then insert it into the intended container. Creation by itself does not display anything on the page.
Append can append nodes or strings; appendChild accepts a node. Appending an already connected node moves that node rather than cloning it. CloneNode creates a copy under its rules, but does not automatically copy event listeners registered with addEventListener.
A DocumentFragment can group nodes before insertion. It is a useful organization tool, not a guarantee that any particular application becomes faster. Match the approach to the number of updates and the code’s clarity.
const item = document.createElement("li");
const article = document.createElement("article");
const title = document.createElement("h4");
title.className = "card-title";
title.textContent = "HTML Workshop";
article.append(title);
item.append(article);
preview.append(item);The lab creates a list item containing an article, heading, note and badge. It sets the user’s text as textContent before insertion and limits the preview to six cards.
ParentElement, children and nextElementSibling work with element relationships. ChildNodes and nextSibling can also include text or comment nodes. A formatting newline can therefore be the next sibling even when the next visible card is an element.
Closest searches the element itself and then its ancestors for a matching selector. QuerySelector searches matching descendants. These directions matter when locating the containing card or a title inside it.
Keep traversal scoped and purposeful. A handler should not climb until it finds any article in the entire document if the intended card belongs only to the preview.
const title = card.querySelector(".card-title");
const owner = title.closest("article");
console.log(owner === card); // true in this structure
console.log(preview.children.length); // current element childrenThe lab’s empty-state paragraph is outside the list. That keeps the list’s element children aligned with its actual card count.
Element.remove disconnects that element from its parent. A JavaScript variable can still refer to the detached object afterward. Removing an element from the document does not automatically erase every reference or listener associated with it.
ReplaceChildren replaces a container’s children with the supplied nodes or text. It is useful for a deliberate reset, but replacing a subtree can remove focused controls, selection state and node-specific listeners. A targeted update often preserves more useful state.
The editor adds, edits and removes preview nodes directly. Reset intentionally rebuilds only the preview cards and selection options. The input controls, status region, lesson content and global shell remain in place.
const cardItem = card.closest("li");
cardItem.remove();
// The item is detached; a variable may still refer to it.
preview.replaceChildren(); // clear only this containerWhen the selected card is removed, select the next remaining card, or the previous one when appropriate. If none remain, show an empty message and focus the title input so a new card can be created.
A successful DOM mutation should remain understandable to the person using the page. Use labeled native controls, a concise status region and a meaningful focus path. An article is content; it does not need to become a pretend button just to select it.
Do not move focus after every ordinary text update. Save and highlight keep the action control available. Removing the last card disables actions that require a selected card, so the lab deliberately returns focus to the editable title field.
A role="status" region announces concise updates in supporting assistive technology. Replacing an entire live preview on every keystroke can be noisy. The editor runs on explicit buttons and announces one outcome rather than broadcasting the full DOM snapshot.
status.textContent = "Card removed. Two cards remain.";
if (remainingCards.length > 0) {
selector.focus();
} else {
titleInput.focus();
}Native keyboard and assistive-technology behavior should still be tested in a browser. A correct attribute or DOM snapshot alone is not a full accessibility result. Event propagation and form validation are developed further in Level 17.
GetBoundingClientRect reports an element’s box relative to the viewport. Scrolling can change its reported position. Window.innerWidth describes the viewport; scrollHeight and clientHeight answer different questions about content and visible area. Do not infer a card’s layout from its title length alone.
Reading layout after frequent writes can have a performance cost. Prefer a clear update followed by the measurements you actually need, rather than repeatedly alternating tiny writes and reads inside a large loop.
A local DOM editor does not save to GitHub or a server. Storage APIs offer deliberate client persistence, but storage can be unavailable and stored text needs validation when read back. This lab intentionally resets its preview on page reload; only the course’s completion marker uses the established local storage behavior.
const box = card.getBoundingClientRect();
console.log(box.width, box.height);
console.log(box.top); // viewport-relative positionUse the browser’s Elements and accessibility inspection tools to review the live structure, computed styles and properties. View Source usually shows delivered HTML rather than every subsequent DOM mutation.
Follow a fixed card-creation example through selection, creation, population, insertion and inspection. Use the approved timeline and playback controls. This visualizer explains the operations; the live lab below performs actual preview updates.
Use a fixed ID and check that the preview exists.
document.getElementById("preview")A null result must be handled. The operation stays scoped to the preview, not the global page shell.
Select a card, edit its title and note, then save the text. Add a new card from the fields, toggle its highlight or remove the selected card. The preview uses actual list items and articles built with DOM methods; the inspector reads their current text and class state.
Titles must contain 1–60 characters after trimming; notes may be blank and are limited to 180 characters. The preview holds at most six cards. Duplicate titles are allowed because identity comes from generated IDs. Angle brackets remain literal text.
Remove all cards to test the empty state, then add one again. Reset restores the three starting cards and their unhighlighted state. Changes are local to this page visit and are not saved to the website repository.
Preview is ready.
No cards remain. Enter a title and add a new card.
Check IDs, selectors, scope and null results.
Check whether the required nodes exist at initialization.
Inspect the leaf updated and whether markup was unintentionally parsed.
Check disabled controls, pressed state, focus and current counts.
If an added card is absent from a saved NodeList, perform a fresh query before assuming append failed. If editing deletes the note, inspect whether textContent was assigned to the entire article. If disabled="false" leaves a button disabled, use the boolean property or remove the attribute.
If a reset causes an action to fire twice, inspect duplicate listener registration. If removing a card loses the keyboard path, decide which surviving control should receive focus. If the inspector reports data but the card is invisible, inspect CSS and rendering rather than claiming the DOM count proves visual success.
Use a minimal failing sequence: add one card, edit its title, toggle highlight, remove it, then reset. Confirm each operation changes only the intended preview and that the source page remains intact.
No. It changes the live document instance. Repository or server persistence requires a separate deliberate mechanism.
Null. Check it before accessing properties; an invalid selector can instead throw a SyntaxError.
No. Its NodeList membership is static. Query again to include newly inserted matching elements.
It supplies literal text rather than parsing markup. Target the heading so unrelated article children survive.
No. A boolean disabled attribute is active when present. Set the disabled property to false or remove the attribute.
It moves that node to the new location rather than automatically cloning it.
No. The node is disconnected, but existing references can still point to that object.
Show a clear empty state, disable actions requiring a selection and preserve a usable focus path, such as the title field for adding a card.
Save a title containing tags as typed text. Expected: the brackets appear literally; no nested strong element is created.
Add one card to the three starting cards. Expected: four list items and a unique generated ID.
Toggle twice. Expected: class and badge turn on, then off; aria-pressed follows the selected state.
Edit a heading. Expected: its note and generated ID remain available.
Remove every card. Expected: count zero, clear empty message and disabled selection actions.
Reset after editing and removing. Expected: three original unhighlighted cards.
Save querySelectorAll results, insert a card, then query again. Expected: saved membership stays unchanged; fresh membership includes the addition.
Use Tab and native buttons to remove the last card, then add one. Expected: focus remains on a useful surviving control.
The object representation and APIs for the document tree.
An object in the document tree, such as an element or text node.
A node representing an HTML or other markup element.
A pattern used to identify matching elements.
A collection of nodes; querySelectorAll supplies a static one.
A named value in an element’s attribute representation.
A field on a DOM object that may expose current state.
The string-valued interface to an element’s data attributes.
A node disconnected from the document or its previous parent.
The current target for keyboard interaction in the document.
Build a labeled card editor with a list preview. Generate stable IDs, set user text with textContent, and create semantic list items containing articles. Add, edit, highlight and remove cards without replacing unrelated page content.
Keep a six-card teaching limit, title and note limits, synchronized disabled/pressed states and a clear zero-card outcome. Target the title and note for edits. Use a fresh query for the inspector. Announce a concise result and choose a useful focus target when removing the last selected card.
Test literal angle brackets, duplicate titles, a rejected blank title, six-card capacity, removal to zero, addition after zero and reset. Inspect node structure, visible text, class state and keyboard behavior. State explicitly whether the data persists after reload.
Success criterion: explain each update in terms of selection, node/property changes and connection to the live document. Level 17 will deepen events, propagation and form validation.
Before marking complete, create a scoped preview update, distinguish text from markup, handle missing selections and demonstrate the empty-state keyboard path. Completion is a local study marker rather than an assessment score.