docs

Walking the Node Tree

Step through every node in the DOM tree to inspect text and comments.

On this page

Every item in your page is a dom::Node, including the text and comments between tags. Element queries skip those items— dom::Node reaches every child in the tree.

The examples use this HTML markup with the View's dom::Document named document:

HTML
<p id="greeting">Howdy <b>world</b>!</p>

Nodes and Elements

Every element is a dom::Node, but a node can also represent plain text or a comment. To inspect a node or convert it to an element, use its member methods:

C++
auto greeting = document.getElementById("greeting");

dom::Node first = greeting.firstChild();
Log(first.nodeName());  // "#text" (the "Howdy " text)

dom::Element bold = first.nextSibling().AsElement();
Log(bold.tagName());    // "B"

nodeType() returns the kind of node (such as dom::NodeType::Element, dom::NodeType::Text, or dom::NodeType::Comment). nodeName() returns #text for text nodes, #comment for comments, and the uppercase tag name for elements.

AsElement() narrows a node to a dom::Element— it returns an empty handle for text and comments.

👍 Whitespace Creates Text Nodes

Whitespace between HTML tags becomes a text node. Calling firstChild() on an indented element usually returns spaces and a newline— use firstElementChild() to skip straight to the first child element.

Walking the Tree

Traversal Methods

You can traverse the tree using calls that return every node kind or element calls that skip text and comments.

Relationship Node Calls (Every Kind) Element Calls (Elements Only)
First child firstChild() firstElementChild()
Last child lastChild() lastElementChild()
Siblings previousSibling(), nextSibling() previousElementSibling(), nextElementSibling()
Parent parentNode() parentElement()
All children childNodes() children(), childElementCount()

To check whether a node has children of any kind, call hasChildNodes().

Iterating Child Nodes

To step through every child node in order, iterate over childNodes():

C++
for (dom::Node child : greeting.childNodes()) {
  if (child.nodeType() == dom::NodeType::Text) {
    std::string text = child.nodeValue;  // "Howdy ", then "!"
    Log(text);
  }
}

Both childNodes() and children() return snapshots rather than live collections— call them again to inspect changes made after the query.

Text and Comment Nodes

Creating Text and Comments

You can create standalone text and comment nodes on the document, then insert them using standard tree insertion methods:

C++
auto label = document.getElementById("score");
dom::Node score = document.createTextNode("0");
label.appendChild(score);
label.before(document.createComment("updated by the HUD"));

Strings passed to methods like append(), prepend(), before(), after(), or replaceWith() become text nodes automatically (the string is never parsed as markup). For insertion calls, see Finding and Modifying Elements.

Changing Text in Place

To update a text node in place, assign to nodeValue:

C++
score.nodeValue = std::to_string(points);

Holding a handle to a text node lets you update its value without modifying other children (assigning to textContent on the parent element replaces all children).

On elements, nodeValue reads as an empty string and ignores writes.

Checking If a Node Is in the Page

To check if a node is currently connected, call isConnected():

C++
auto toast = document.createElement("div");
toast.isConnected();  // false

document.body().appendChild(toast);
toast.isConnected();  // true

toast.remove();
toast.isConnected();  // false

A newly created node isn't connected until you insert it into the page.

Calling remove() disconnects a node from its parent, but its C++ handle remains valid so you can insert it again later.

Documents and Frames

The Document

The dom::Document provides direct access to top-level elements like documentElement() (the root <html>), body(), head(), and activeElement() (the focused element).

It also supports tree traversal calls like firstElementChild(), children(), hasChildNodes(), and childNodes() (which includes the doctype).

To navigate from the root element back to the document, call ownerDocument():

C++
dom::Element root = document.documentElement();

root.parentNode();     // empty
root.ownerDocument();  // the document

📘 The Document Is Not a Node

Unlike the web DOM, dom::Document is its own type and doesn't inherit from dom::Node. Calling parentNode() on the root <html> element returns an empty handle— use ownerDocument() to reach the document from any node.

Frame Documents

Each <iframe> or <frame> element contains its own document.

To access an iframe's document, convert the element with AsIFrame() and call contentDocument():

C++
auto frame = document.querySelector("iframe#shop").AsIFrame();
dom::Document shop = frame.contentDocument();
auto cart = shop.getElementById("cart");

The top-level page's document comes from dom::Document(view).

🚧 Frame Handles Expire on Navigation

A frame document's handles stop working when the frame navigates or is removed from the page.

Differences from JavaScript

While the C++ DOM API mirrors JavaScript, a few types and behaviors differ.

JavaScript C++
node.childNodes and el.children are live collections childNodes() and children() return snapshots (call again after changes)
Read-only properties (node.parentNode, node.isConnected) Method calls (parentNode(), isConnected())
The document is a Node (nodeType 9, where html.parentNode === document) dom::Document is its own type (parentNode() on <html> is empty)
Text and Comment objects have data Single dom::Node type with nodeValue
Missing nodes return null Returns an empty handle