Chapter 39: DOM Navigating
XML DOM – Navigating Nodes (also called “traversing” or “walking the tree”).
I will explain it as if I’m your personal teacher sitting next to you — drawing the tree on a whiteboard, showing every relationship with small arrows, giving everyday analogies, pointing out traps beginners fall into, and providing realistic JavaScript code you can copy-paste and experiment with immediately.
1. What does “navigating nodes” actually mean?
Navigating = moving from one node to another using the relationships between them.
The DOM tree has three main directions you can move:
- Up → to the parent
- Down → to children
- Sideways → to siblings (previous / next)
You navigate when:
- You want to go from a <price> tag up to its parent <book>
- You want to list all direct children of <book>
- You want to find the next <book> after the current one
- You want to visit every node in the document (recursive traversal)
2. The complete set of navigation properties (your “map”)
| Direction | Property | Returns | Includes text/comment nodes? | Live? | Returns null when… | Most common use case |
|---|---|---|---|---|---|---|
| Up | parentNode | Node or null | — | Yes | At root (document) | General-purpose parent access |
| Up | parentElement | Element or null | — | Yes | At root or parent is not an element | When you know parent must be an element |
| Down | childNodes | NodeList | Yes | Yes | — | Need everything inside (text + elements + …) |
| Down | children | HTMLCollection | No | Yes | — | Only want element children |
| Down first | firstChild | Node or null | Yes | Yes | No children | First node (often whitespace text) |
| Down first | firstElementChild | Element or null | No | Yes | No element children | First real element child |
| Down last | lastChild | Node or null | Yes | Yes | No children | Last node |
| Down last | lastElementChild | Element or null | No | Yes | No element children | Last real element child |
| Side → next | nextSibling | Node or null | Yes | Yes | Last sibling | Next node at same level (often whitespace) |
| Side → next | nextElementSibling | Element or null | No | Yes | Last element sibling | Next real element sibling |
| Side ← prev | previousSibling | Node or null | Yes | Yes | First sibling | Previous node |
| Side ← prev | previousElementSibling | Element or null | No | Yes | First element sibling | Previous real element sibling |
Golden rules to remember forever
- If you want only elements → use properties that have “Element” in the name (children, firstElementChild, nextElementSibling, …)
- If you want everything (including whitespace text nodes, comments, etc.) → use the properties without “Element” (childNodes, firstChild, nextSibling, …)
- Whitespace matters Newlines and spaces between tags are real Text nodes → very often firstChild is a text node, not the first element.
3. Visual example – Real XML and its navigation paths
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<library> <book id="101"> <title>Atomic Habits</title> <author>James Clear</author> <price currency="INR">499.00</price> </book> <book id="102"> <title>Rich Dad Poor Dad</title> </book> </library> |
Let’s pick the second <title> node (Rich Dad Poor Dad) and see where we can go:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Starting node: <title>Rich Dad Poor Dad</title> Up: parentNode / parentElement → <book id="102"> Down: firstChild / lastChild → Text node "Rich Dad Poor Dad" firstElementChild / lastElementChild → null (no children) Sideways (siblings): previousElementSibling → null (first child of its parent) nextElementSibling → null (last child of its parent) From the <book id="102">: nextElementSibling → <book id="101"> ? No — previous one previousElementSibling → null |
4. Practical code – All navigation properties demonstrated
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// Assume xmlDoc is already parsed const secondBook = xmlDoc.querySelectorAll("book")[1]; // <book id="102"> const title = secondBook.querySelector("title"); // ──────────────────────────────────────── // UP – to parent // ──────────────────────────────────────── console.log("Parent tag:", title.parentElement.tagName); // "book" console.log("Parent id:", title.parentElement.getAttribute("id")); // "102" // ──────────────────────────────────────── // DOWN – children // ──────────────────────────────────────── console.log("All children count:", title.childNodes.length); // usually 1 (text) console.log("First child nodeType:", title.firstChild.nodeType); // 3 = Text console.log("Text:", title.firstChild.textContent.trim()); // "Rich Dad Poor Dad" console.log("Element children count:", title.children.length); // 0 console.log("First element child:", title.firstElementChild); // null // ──────────────────────────────────────── // SIDEWAYS – siblings // ──────────────────────────────────────── console.log("Next element sibling:", title.nextElementSibling); // null // Go to first book and check its next sibling const firstBookTitle = xmlDoc.querySelector("book title"); console.log("Next element after first title:", firstBookTitle.nextElementSibling.tagName); // "author" |
5. Realistic patterns – How people really navigate in projects
Pattern 1 – Get only meaningful children (skip whitespace)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
function getCleanChildren(parent) { return Array.from(parent.childNodes) .filter(node => node.nodeType === 1); // only elements } const book = xmlDoc.querySelector("book"); const cleanKids = getCleanChildren(book); console.log(cleanKids.map(el => el.tagName)); // ["title", "author", "price"] |
Pattern 2 – Build full path from any node to root
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function getNodePath(node) { const path = []; let current = node; while (current && current.nodeType !== 9) { // stop at Document if (current.nodeType === 1) { let part = current.tagName.toLowerCase(); if (current.hasAttribute("id")) { part += `#{current.getAttribute("id")}`; } path.unshift(part); } current = current.parentNode; } return "/" + path.join("/"); } console.log(getNodePath(xmlDoc.querySelector("price"))); // → /library/book/price |
Pattern 3 – Loop through siblings
|
0 1 2 3 4 5 6 7 8 9 10 |
let current = xmlDoc.querySelector("title"); while (current) { console.log(current.tagName || current.nodeType); current = current.nextElementSibling; } |
6. Common traps & how to avoid them
Trap 1 – Assuming firstChild is always an element
|
0 1 2 3 4 5 6 |
console.log(book.firstChild.tagName); // undefined — it's a text node (whitespace) |
Fix: book.firstElementChild
Trap 2 – Modifying tree while iterating childNodes
|
0 1 2 3 4 5 6 7 8 |
for (let child of parent.childNodes) { child.remove(); // dangerous — collection changes live } |
Fix: copy to array first
|
0 1 2 3 4 5 6 7 8 9 |
const snapshot = Array.from(parent.childNodes); for (let child of snapshot) { child.remove(); } |
Trap 3 – Forgetting that textContent includes all descendants
|
0 1 2 3 4 5 6 |
console.log(book.textContent); // "Atomic HabitsJames Clear499.00" — no spaces, all concatenated |
Fix: walk children manually if you need structure
Summary – Navigation Quick Reference
| You want to move… | Recommended property | Returns | Includes whitespace? | Tip / Note |
|---|---|---|---|---|
| Up to parent | parentElement | Element or null | — | Preferred over parentNode |
| Down – all nodes | childNodes | NodeList | Yes | Contains text nodes, comments |
| Down – only elements | children | HTMLCollection | No | Usually what you want |
| First / last child | firstChild / lastChild | Node | Yes | Often whitespace |
| First / last element child | firstElementChild / lastElementChild | Element | No | Skip whitespace & comments |
| Next / prev sibling | nextSibling / previousSibling | Node | Yes | Often whitespace |
| Next / prev element sibling | nextElementSibling / previousElementSibling | Element | No | Most useful for walking elements only |
Would you like to continue with one of these practical next steps?
- Recursive full-tree traversal (print whole document structure)
- Build path from any node to root (very useful for debugging)
- Collect all text while preserving structure
- Clean traversal (skip whitespace text nodes automatically)
- Modify the tree while navigating (add/remove/move nodes)
- Real-world examples: RSS feed traversal, SOAP envelope, configuration file
Just tell me which direction you want to explore next! 😊
