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:
<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:
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— usefirstElementChild()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():
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:
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:
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():
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():
dom::Element root = document.documentElement();
root.parentNode(); // empty
root.ownerDocument(); // the document
📘 The Document Is Not a Node
Unlike the web DOM,
dom::Documentis its own type and doesn't inherit fromdom::Node. CallingparentNode()on the root<html>element returns an empty handle— useownerDocument()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():
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 |