So I am trying create a function, that will console.log the entire DOM tree of the HTML page. It is supposed to output the tag name of each HTML element in the order they appear - children before siblings + attribute name.
I have this working piece of code:
"use strict";
document.addEventListener("DOMContentLoaded", traverse);
function traverse() {
let elm = document.documentElement;
displayInfo(elm);
};
function displayInfo(elm){
//console.log(elm)
/* if (elm.childElementCount>0) {
let children = Array.from(elm.children);
console.log(children);
children.forEach( displayInfo );
}; */
if (elm.hasAttributes) {
//console.log(elm.attributes[0]);
};
var c = elm.childNodes;
let i;
for (i = 0; i < c.length; i++) {
console.log(c[i].nodeName);
if (c[i].childElementCount>0) {
//console.log(c[i].childElementCount);
if (c[i].hasAttributes) {
//console.log(c[i].attributes[0]);
};
let cc = c[i].children;
let ii;
for (ii=0; ii < cc.length; ii++) {
console.log(cc[ii].nodeName);
if (cc[ii].hasAttributes) {
//console.log(cc[ii].attributes[0]);
};
if (cc[ii].childElementCount>0) {
//console.log(cc[ii].childElementCount);
let ccc = cc[ii].children;
let iii;
for (iii=0; iii < ccc.length; iii++) {
console.log(ccc[iii].nodeName);
if (ccc[iii].hasAttributes) {
//console.log(ccc[iii].attributes[0]);
};
if (ccc[iii].childElementCount>0) {
//console.log(ccc[iii].childElementCount);
let cccc = ccc[iii].children;
let iiii;
for (iiii=0; iiii < cccc.length; iiii++) {
console.log(cccc[iiii].nodeName);
if (cccc[iiii].hasAttributes) {
//console.log(cccc[iiii].attributes[0]);
};
if (cccc[iiii].childElementCount>0) {
console.log(cccc[iiii].childElementCount)
}
}
}
}
}
}
}
}
};
The problem is, I am horribly repeating myself, plus I am manually setting how "deep" it will traverse. Is there a way to declare only one function that traverse?
PS: I know it's horrible to look at, I was just making sure the function would "work" all the way.
So I am trying create a function, that will console.log the entire DOM tree of the HTML page. It is supposed to output the tag name of each HTML element in the order they appear - children before siblings + attribute name.
I have this working piece of code:
"use strict";
document.addEventListener("DOMContentLoaded", traverse);
function traverse() {
let elm = document.documentElement;
displayInfo(elm);
};
function displayInfo(elm){
//console.log(elm)
/* if (elm.childElementCount>0) {
let children = Array.from(elm.children);
console.log(children);
children.forEach( displayInfo );
}; */
if (elm.hasAttributes) {
//console.log(elm.attributes[0]);
};
var c = elm.childNodes;
let i;
for (i = 0; i < c.length; i++) {
console.log(c[i].nodeName);
if (c[i].childElementCount>0) {
//console.log(c[i].childElementCount);
if (c[i].hasAttributes) {
//console.log(c[i].attributes[0]);
};
let cc = c[i].children;
let ii;
for (ii=0; ii < cc.length; ii++) {
console.log(cc[ii].nodeName);
if (cc[ii].hasAttributes) {
//console.log(cc[ii].attributes[0]);
};
if (cc[ii].childElementCount>0) {
//console.log(cc[ii].childElementCount);
let ccc = cc[ii].children;
let iii;
for (iii=0; iii < ccc.length; iii++) {
console.log(ccc[iii].nodeName);
if (ccc[iii].hasAttributes) {
//console.log(ccc[iii].attributes[0]);
};
if (ccc[iii].childElementCount>0) {
//console.log(ccc[iii].childElementCount);
let cccc = ccc[iii].children;
let iiii;
for (iiii=0; iiii < cccc.length; iiii++) {
console.log(cccc[iiii].nodeName);
if (cccc[iiii].hasAttributes) {
//console.log(cccc[iiii].attributes[0]);
};
if (cccc[iiii].childElementCount>0) {
console.log(cccc[iiii].childElementCount)
}
}
}
}
}
}
}
}
};
The problem is, I am horribly repeating myself, plus I am manually setting how "deep" it will traverse. Is there a way to declare only one function that traverse?
PS: I know it's horrible to look at, I was just making sure the function would "work" all the way.
Share Improve this question asked May 12, 2018 at 10:30 the_odorthe_odor 551 silver badge8 bronze badges 2-
Rather than
if (condition) { big block }
, you canif (!condition) continue;
to reduce indentation hell. – CertainPerformance Commented May 12, 2018 at 10:31 - And how would that look like in the example? I am sorry I am still not entirely familiar with the if statements – the_odor Commented May 12, 2018 at 10:34
3 Answers
Reset to default 4There is an API just for this which is way more powerful than anything you could try to build yourself: TreeWalker.
var walker = document.createTreeWalker(
document.documentElement,
NodeFilter.SHOW_ELEMENT // only elements
);
while (walker.nextNode()) {
let current = walker.currentNode;
console.log(
current.tagName,
[...current.attributes].map(({value,name}) => `${name}=${value}`).join()
);
}
<article>
<div id="container">container content
<span>one span</span>
<span class="nest1">nest1 span<span class="nest2">nest2 span</span></span>
</div>
</article>
Just wanted to add this simple log of all nodes in a document object element names id and classes included placed in a neat array.
console.log(document.getElementsByTagName('*'));
Note that hasAttributes
is a function, not a property. element.hasAttributes
will always be true
.
Use recursion, like this:
function displayInfo(node) {
console.log(node.nodeName);
if (node.nodeType === 3) {
console.log('Text node: ' + node.textContent);
return;
}
if (node.hasAttributes()) {
[...node.attributes].forEach(({ name, value }) => console.log(`${name}=${value}`));
}
node.childNodes.forEach(displayInfo);
}
displayInfo(document.documentElement);
<article>
<div id="container">container content
<span>one span</span>
<span class="nest1">nest1 span<span class="nest2">nest2 span</span></span>
</div>
</article>
The continue
shortcut I mentioned would look like this, to start with:
for (i = 0; i < c.length; i++) {
console.log(c[i].nodeName);
if (c[i].childElementCount === 0) continue;
//console.log(c[i].childElementCount);
if (c[i].hasAttributes) {
//console.log(c[i].attributes[0]);
};
let cc = c[i].children;
Avoid indentation when you can - you'll make your code a lot more readable.