I need help using recursion to navigate every element in the DOM - javascript

I need to use recursion to navigate every element in the DOM, and for every body element, determine if it is an element node. If it is, I need to add a child node to it. I need to use JavaScript for this assignment. Here is what I have so far in my JavaScript file:
window.addEventListener("load", function() {
var highlightButton = document.getElementById("highlight");
highlightButton.addEventListener('click', search);
function search(node) {
if (node.nodeType === 1) {
var spanEl = document.createElement("span");
spanEl.className = "hoverNode";
node.appendChild(spanEl);
spanEl.innerHTML = spanEl.parentNode.tagName;
}
}
})
I understand how to append a child node, but I don't know how to traverse the entire DOM and append the child node to every element.

Given "every body element" actually means "every element in the body", you can start with an element and get all its child elements. Loop over the child elements and if any is type 1 and has a child nodes, you call the function again with the element.
If it doesn't have children, go to the next child, etc. The following is just an example of recursing over all the nodes and picking out just the type 1s. You can modify it to do whatever.
// Call with a document or HTML element
function checkBodyElements(node) {
// Recursive function
function traverseBody(node) {
if (node.childNodes.length) {
// Loop over every child node
node.childNodes.forEach(child => {
// If it's a type 1, call the function recursively
if (child.nodeType == 1) {
console.log(child.tagName, child.nodeType)
traverseBody(child);
}
});
}
}
// Get the body element
let body = node.querySelector('body');
// If a body element was found, traverse its children
if (body) {
traverseBody(body);
}
}
window.onload = checkBodyElements(document);
<div>
<div>
<p><span></span>
</p>
</div>
<div>
<p><span></span>
</p>
</div>
</div>

Is there any specific reason that implies the creation of a recursive function, other than to support older browsers, like IE6 and 7?
If no, you could simply use document.body.querySelectorAll('*') to select every element node in the DOM while ignoring the ones outside the body element. An example would be:
window.addEventListener('load', function () {
var highlightButton = document.getElementById("highlight");
function search () {
document.body.querySelectorAll('*').forEach(function (el) {
var spanEl = document.createElement('span');
spanEl.innerHTML = el.tagName;
spanEl.className = 'hoverNode';
el.appendChild(spanEl);
});
}
highlightButton.addEventListener('click', search);
});
If yes, then an option would be:
window.addEventListener('load', function () {
var highlightButton = document.getElementById("highlight");
// traverse downwards from rootEl while excluding Comment elements on IE6 7 & 8
function traverseDOMFrom (rootEl, iterator) {
if (!rootEl || rootEl.nodeType !== 1 || typeof iterator !== 'function') {
return;
}
if (rootEl.children && rootEl.children.length > 0) {
Array.prototype.slice.call(rootEl.children).forEach(function (el) {
traverseDOMFrom(el, iterator);
});
}
iterator(rootEl);
}
function search () {
traverseDOMFrom(document.body, function (el) {
var spanEl = document.createElement('span');
spanEl.innerHTML = el.tagName;
spanEl.className = 'hoverNode';
el.appendChild(spanEl);
});
}
highlightButton.addEventListener('click', search);
});
Notice that in both cases, a polyfill for Array.prototype.forEach() and for EventTarget.addEventListener() would be necessary if you want to support those features on IE6, 7 and 8! Otherwise you could also achieve the same results by iterating the element's array with a custom for loop instead, as for this .addEventListener, a simple .onload event handler could be used if there's no need to support multiple listeners.

Related

Intersection Observer and removing the observed element

I'm using intersection observer in a marquee, where I am observing for when the first child leaves the viewport, removing that first child, shifting the new (identical) first child by its element's width to the right (with marginLeft), and then appending another identical child element to the end of the parent div's children.
This works once, but intersection observer does not fire a second time, even though the new first child is identical to the deleted one.
I was wondering whether mutation observer would be more useful here, but not sure how to go about it!
let firstChild = document.querySelector('.marquee').firstElementChild;
let target = firstChild;
let options = {
root: null,
threshold: 0,
};
function onChange(changes) {
changes.forEach(change => {
if (change.intersectionRatio === 0) {
firstChild.remove();
console.log('Header is outside viewport');
for ( i = 0; i < 100000; i++ ) {
if ( rectParent.childElementCount < 3 ) {
let child = document.createElement('p');
child.className = "global-chat";
child.innerHTML = "Something written here";
let docFrag2 = document.createDocumentFragment().appendChild(child.cloneNode(true));
rectParent.appendChild(docFrag2);
document.querySelector('.marquee').firstElementChild.style.marginLeft = `${boxWidth}` * `${i+1}` + "px";
}
}
}
});
}
let observer = new IntersectionObserver(onChange, options);
observer.observe(target);```
The Problem
you define the IntersectionObserver class and instantiate it in the observer. at this moment, the firstChild variable gets the proper element, but in the next change, your firstChild didn't get an update!
The solution
You can update your firstChild element in the onChange function to ensure get the proper element before changes.
for example:
function onChange(changes) {
changes.forEach(change => {
if (change.intersectionRatio === 0) {
const firstChild = document.querySelector('.marquee').firstElementChild;
firstChild.remove();
// rest of the codes ...
}
});
}

How to check function to see if parent exists

Working on a JS function: How would I check to see if a parent element exists and if it doesn't print out 'not found'. Also how do I check for a parent element with a certain className?
var findParentByClassName = function(element, targetClass) {
if (element) {
var currentParent = element.parentElement;
while (currentParent.className !== targetClass && currentParent.className !== null) {
currentParent = currentParent.parentElement;
} // closes loop
return currentParent;
} // closes if statement
};
I was thinking to write this:
if(element.parentElement !== targetClass) {
console.log('Parent not found');
}
This can be an example of closest() and get() Jquery functions. Of course everything in the DOM has a parent, so we will check for something special, a form with class="myclass":
var element = $(yourElement).closest('form.myclass');
if(element.get(0)){
//you got your element
}
else{
console.log('not found');
}
The method get([index]) is needed due, the selector won't return empty.
If you want to check if immediate parent has certain class, you can do it several ways:
Pure JavaScript (best performance):
if (element.parentElement.classList.contains('nameOfClass')) {}
jQuery (best jQuery performance):
if ($(element).parent().hasClass('nameOfClass')) {}
jQuery (usable for every selector, not just class, worse performance):
if ($(element).parent('.nameOfClass').length) {}

How to recursively search all parentNodes

I want to know, how to find out recursively all parent nodes of an element.
Suppose i have following snippet
<font>Hello</font>
In this I would like to find out whether font tag's parent node is an anchor tag or not.
Now this can be achieved by simply checking .parentNode property. But what if there are following cases like,
<font><b>Hello<b></font>
or
<font><b><u>Hello</u><b></font>
So, basically, how to know if we have reached the top most parent node ?
You can traverse from an element up to the root looking for the desired tag:
function findUpTag(el, tag) {
while (el.parentNode) {
el = el.parentNode;
if (el.tagName === tag)
return el;
}
return null;
}
You call this method with your start element:
var el = document.getElementById("..."); // start element
var a = findUpTag(el, "A"); // search <a ...>
if (a) console.log(a.id);
The following recursive function will return an ascending ordered array, with all the parents nodes for the provided DOM element, until BODY node is reached.
function parents(element, _array) {
if(_array === undefined) _array = []; // initial call
else _array.push(element); // add current element
// do recursion until BODY is reached
if(element.tagName !== 'BODY' ) return parents(element.parentNode, _array);
else return _array;
}
Usage :
var parentsArray = parents( document.getElementById("myDiv") );
You can use jQuery closest() method to get the closest ahref:
$("#your-element").closest("a").css("color", "red");
Or you can have a look at the parentsUntil method:
$("#your-element").parentsUntil("#yourWrapper", "a").first().css("color", "red");
Try it out here: http://www.lunarailways.com/demos/parents.html
I been working on similar thing. Trying to close a div if user clicks outside the div. It needs to loop through all its parent nodes.
have a look at this:
http://jsfiddle.net/aamir/y7mEY/
Here's a shorter one:
function parentByTag(el, tag) {
if(!el || el.tagName == tag) {
return el
} else {
return parentByTag(el.parentElement, tag)
}
}
Returns undefined if not found.

jQuery convert DOM element to different type

I need to convert a DOM element to a different type (as in HTML tag name, a to p in this case), but still retain all the original elements attributes. Whether they are valid for the new type or not doesn't matter in this case.
Any suggestions on how to do this?
I've looked at just creating a new element and copying the attributes across, but this isn't without it's own complications. In Firefox, DOMElement.attributes helpfully only includes attributes with a value, but in IE it reports all possible attributes for that element. The attributes property itself is read-only, so no way to copy that.
Sans-jQuery solution:
function makeNewElementFromElement( tag, elem ) {
var newElem = document.createElement(tag),
i, prop,
attr = elem.attributes,
attrLen = attr.length;
// Copy children
elem = elem.cloneNode(true);
while (elem.firstChild) {
newElem.appendChild(elem.firstChild);
}
// Copy DOM properties
for (i in elem) {
try {
prop = elem[i];
if (prop && i !== 'outerHTML' && (typeof prop === 'string' || typeof prop === 'number')) {
newElem[i] = elem[i];
}
} catch(e) { /* some props throw getter errors */ }
}
// Copy attributes
for (i = 0; i < attrLen; i++) {
newElem.setAttribute(attr[i].nodeName, attr[i].nodeValue);
}
// Copy inline CSS
newElem.style.cssText = elem.style.cssText;
return newElem;
}
E.g.
makeNewElementFromElement('a', someDivElement); // Create anchor from div
while not a complete solution, the logic would basically be:
Save your existing element:
var oldElement = $(your selector here);
create a new element and insert it just before or after your oldElement
copy the attributes
oldElement.attr().each(function(){
copy old
});
better yet, here is an example of a plug-in that does just what you want:
http://plugins.jquery.com/project/getAttributes
A more modern (2020+) approach is:
function changeTag (element, tag) {
// prepare the elements
const newElem = document.createElement(tag)
const clone = element.cloneNode(true)
// move the children from the clone to the new element
while (clone.firstChild) {
newElem.appendChild(clone.firstChild)
}
// copy the attributes
for (const attr of clone.attributes) {
newElem.setAttribute(attr.name, attr.value)
}
return newElem
}
Compared to #James answer, you don't need to copy the cssText because it's already taken care by the browser. You also don't need the string/number dom properties since those are also properly migrated. It's also best to work on the cloned node only, not both of them (clone and elem)
The following replaceTag(element, tagName) function replaces the provided element with a new element of type tagName, e.g. replaceTag(someElement, 'p').
// BEGIN actual function
function replaceTag(element, tagName) {
const newElement = document.createElement(tagName);
newElement.append(...element.childNodes);
for (const attribute of element.attributes) {
newElement.setAttribute(attribute.name, attribute.value);
}
element.replaceWith(newElement);
return newElement;
}
// END actual function
// BEGIN from here onwards, example usage code
function divSpan2PEm() {
const divElements = document.querySelectorAll('div');
for (divElement of divElements) {
const containedSpanElements = divElement.querySelectorAll(':scope > span');
for (containedSpanElement of containedSpanElements) {
replaceTag(containedSpanElement, 'em');
}
replaceTag(divElement, 'p');
}
}
div {
background-color: red;
}
[role="complementary"] {
border: 2px solid blue;
}
span {
background-color: orange;
}
[data-bing] {
color: brown;
}
<div role="complementary">
<span>foo <strong>so strong</strong></span>
<span data-bing="true">bar</span>
</div>
<button type="button" onclick="divSpan2PEm()">Convert div-span to p-em</button>
replaceTag moves any existing children to the new element, as well as recreates any attributes. It does not and cannot replace any references to the original element, e.g. attached event handlers. Also, in general the interim and resulting HTML / updated DOM may not be valid.

how to count total number of divs inside another div using javascript

How to count the total number of div elements that are contained in another div using javascript?
The getElementsByTagName() is not only a document method, but one that can run on any DOM element.
element.getElementsByTagName is
similar to
document.getElementsByTagName, except
that its search is restricted to those
elements which are descendants of the
specified element
see more at https://developer.mozilla.org/en/DOM/element.getElementsByTagName
So the actual code that does what you ask is
var container_div = document.getElementById('id_of_container_div');
var count = container_div.getElementsByTagName('div').length;
You can use #davidcmoulton's handy Gist:
https://gist.github.com/davidcmoulton/a76949a5f35375cfbc24
I find it quite useful that it doesn't only count DIVs but also lists the count of all element types of your page.
Here is a copy of the Gist for further reference:
(function (window, undefined) {
// Counts all DOM elements by name & logs resulting object to console.
var forEach = Array.prototype.forEach,
counter = {},
incrementElementCount = function (elementName) {
if (counter.hasOwnProperty(elementName)) {
counter[elementName] += 1;
} else {
counter[elementName] = 1;
}
},
processNode = function (node) {
var currentNode = node;
if (currentNode.nodeType === currentNode.ELEMENT_NODE) {
incrementElementCount(currentNode.nodeName);
if (currentNode.hasChildNodes) {
forEach.call(currentNode.childNodes, function (childNode) {
if (childNode.nodeType === currentNode.ELEMENT_NODE) {
processNode(childNode);
}
});
}
}
};
processNode(window.document.firstElementChild);
console.log(counter);
}(this));
There are many way to count divs element using jquery.
But most popular and simple way are:
$(document).ready(function(){
var divCount = $("div").size();
alert(divCount);
});
AND
$(document).ready(function(){
var divCount = $("div").length;
alert(divCount);
});
Its helpful for you

Categories

Resources