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
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
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;
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>
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);
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>
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>
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!)
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>
(() => {
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 Hitchcock
s answer but using Map instead of object
Results in Map(5) {body => -1, div => 0, img => 1, p => 1, span => 2}