15

How is it possible to iterate through the HTML DOM and list all nodes with there depth in javascript.

Example:

<div>
    <img src="foo.jpg">
    <p>
        <span>bar</span>
    </p>
</div>

would result in

  • div 0
  • img 1
  • p 1
  • span 2
Cœur
  • 37,241
  • 25
  • 195
  • 267
Ka Rl
  • 517
  • 8
  • 15

8 Answers8

17

Write a recursive function which tracks the depth:

function element_list(el,depth) {
    console.log(el+' '+depth);
    for(var i=0; i<el.children.length; i++) {
        element_list(el.children[i],depth+1);
    }
}
element_list(document,0);

As CodeiSir points out, this will also list text nodes, but we can filter them out by testing the nodeType. Variations on this code will allow/ignore other node types as desired.

function element_list(el,depth) {
   if (el.nodeType === 3) return;
Community
  • 1
  • 1
Blazemonger
  • 90,923
  • 26
  • 142
  • 180
  • I took some time to make my answer, this one answer is missing some stuff out. When you actualy use it, it won't present the results he wanted. – CoderPi Nov 24 '15 at 21:35
  • @CodeiSir Seeing as how OP didn't provide any code at all, I wasn't trying to present anything more than a basic answer that would point him in the right direction. Good point about filtering out text nodes, though. – Blazemonger Nov 24 '15 at 21:59
  • I was wondering whether you had edited this since you wrote it, but it appears not. `el.children` will return only the elements, so no "text nodes" will be returned, although it will return `SPAN` elements. You'd have to put `el.childNodes` if you did in fact want the text nodes to be printed out. Other than that, this is simple and functional, although I could wish that `TreeWalker` [https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker], which doesn't involve recursion, had a means of finding out the depth of the current node. – mike rodent Sep 16 '17 at 15:42
  • A sample [code](https://jsbin.com/hepijajiqu/edit?js,console) indenting the output. – Adriano P Jul 13 '21 at 19:16
5

Note that the other answers are/where not realy correct ...

This will also filter out "TEXT" Nodes, and not output the BODY tag.

function getDef(element, def) {
  var str = ""

  var childs = element.childNodes
  for (var i = 0; i < childs.length; ++i) {
    if (childs[i].nodeType != 3) {
      str += childs[i].nodeName + " " + def + "<br />"
      str += getDef(childs[i], def + 1)
    }
  }

  return str
}


// Example
document.body.innerHTML = getDef(document.body, 0)
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>
CoderPi
  • 12,985
  • 4
  • 34
  • 62
3

Yes, you can! You would have to iterate and some logic to create this tree, but for your example, you could do something like:

var tracker = {};
Array.from(document.querySelectorAll("*")).forEach(node => {
    if (!tracker[node.tagName]) tracker[node.tagName] = 1;
    else tracker[node.tagName]++;
});
console.log(tracker);

You can modify this to run on a recrusive subset of childNodes. This just iterates the entire document.

Check this fiddle and open the console to see the output of tracker which counts and lists tag names. To add the depth, just grab the parentNode.length all the way up.

Here's an updated script which I think does the depth count propery;

var tracker = {};
var depth = 0;
var prevNode;
Array.from(document.querySelectorAll("*")).forEach(node => {
    if (!tracker[node.tagName]) tracker[node.tagName] = 1;
    else tracker[node.tagName]++;
    console.log("Node depth:", node.tagName, depth);
    if (node.parentNode != prevNode) depth++;
    prevNode = node;
});
console.log(tracker);
Sterling Archer
  • 22,070
  • 18
  • 81
  • 118
2

getElementDepth returns the absolute depth of the node (starting from the html node), to get the difference of depth between two nodes you can just subtract an absolute depth from another.

function getElementDepthRec(element,depth)
{
 if(element.parentNode==null)
     return depth;
    else
     return getElementDepthRec(element.parentNode,depth+1);
}
    
function getElementDepth(element)
{
    return getElementDepthRec(element,0);
}

function clickEvent() {
    alert(getElementDepth(document.getElementById("d1")));
}
<!DOCTYPE html>
<html>
<body>

<div>
 <div id="d1">
 </div>
</div>

<button onclick="clickEvent()">calculate depth</button>

</body>
</html>
Andrea Gobetti
  • 143
  • 1
  • 8
1

My original solution walked each element up the DOM using a while loop to determine its depth:

var el = document.querySelectorAll('body *'),  //all element nodes, in document order
    depth,
    output= document.getElementById('output'),
    obj;

for (var i = 0; i < el.length; i++) {
  depth = 0;
  obj = el[i];
  while (obj.parentNode !== document.body) {   //walk the DOM
    depth++;
    obj = obj.parentNode;
  }
  output.textContent+= depth + ' ' + el[i].tagName + '\n';
}
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>
<hr>
<pre id="output"></pre>

I've come up with a new solution, which stores the depths of each element in an object. Since querySelectorAll() returns elements in document order, parent nodes always appear before child nodes. So a child node's depth can be calculated as the depth of its parent node plus one.

This way, we can determine the depths in a single pass without recursion:

var el = document.querySelectorAll('body *'),  //all element nodes, in document order
    depths= {                                  //stores the depths of each element
      [document.body]: -1                      //initialize the object
    },
    output= document.getElementById('output');

for (var i = 0; i < el.length; i++) {
  depths[el[i]] = depths[el[i].parentNode] + 1;
  output.textContent+= depths[el[i]] + ' ' + el[i].tagName + '\n';
}
<div>
  <img src="foo.jpg">
  <p>
    <span>bar</span>
  </p>
</div>
<hr>
<pre id="output"></pre>
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
1

Anyone looking for something which iterates through the tree under a node without using recursion* but which also gives you depth (relative to the head node) ... as well as sibling-ancestor coordinates at all times:

function walkDOM( headNode ){
  const stack = [ headNode ];
  const depthCountDowns = [ 1 ];
  while (stack.length > 0) {
    const node = stack.pop();
    console.log( '\ndepth ' + ( depthCountDowns.length - 1 ) + ', node: ');
    console.log( node );

    let lastIndex = depthCountDowns.length - 1;
    depthCountDowns[ lastIndex ] = depthCountDowns[ lastIndex ] - 1;

    if( node.childNodes.length ){
        depthCountDowns.push( node.childNodes.length );
        stack.push( ... Array.from( node.childNodes ).reverse() );
    }

    while( depthCountDowns[ depthCountDowns.length - 1 ] === 0 ){
        depthCountDowns.splice( -1 );
    }
  }
} 

walkDOM( el );

PS it will be understood that I've put in > 0 and === 0 to try to improve clarity... first can be omitted and second can be replaced with leading ! of course.

* look here for the appalling truth about the cost of recursion in JS (contemporary implementations at 2018-02-01 anyway!)

mike rodent
  • 14,126
  • 11
  • 103
  • 157
0

You can do this by using Jquery

$('#divId').children().each(function () {
     // "this" is the current element
});

and the html should be like the following:

<div id="divId">
<img src="foo.jpg">
<p>
    <span>bar</span>
</p>

Ahmad Harb
  • 605
  • 3
  • 17
  • 3
    Introducing jQuery into a question that isn't calling for it is not a good idea. It can be quickly downvoted. Also, this only alert (use console.log) the `value` (which none of those elements has, because they're not input elements) of the elements, not their document node depth. – Sterling Archer Nov 24 '15 at 21:29
  • Yes this is true, but "this" is the current element. I will remove the value – Ahmad Harb Nov 24 '15 at 21:31
0
(() => {
    const el = document.querySelectorAll('body *');
    const depths = new Map();
    depths.set(document.body, -1)
    el.forEach((e) => {
        const p = e.parentNode;
        const d = depths.get(p);
        depths.set(e, d + 1);
    })
    return depths;
})()

This is Rick Hitchcocks answer but using Map instead of object

Results in Map(5) {body => -1, div => 0, img => 1, p => 1, span => 2}

JLarky
  • 9,833
  • 5
  • 36
  • 37