How to recursively search all parentNodes - javascript

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.

Related

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

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.

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) {}

String literal not being appended to DOM

I'm trying to make functions that return me an HTML element as a string so I can eventually append them to the DOM and pass them around my functions. I have paragraph, heading, and div's working but cannot for the life of me seem to get links to work.
This is the non-working codepen
The javascript in question is:
function link(href,text,css){
let target = ``;
if(css == null ){
return target;
}
return addStyles(target,css);
}
My addStyles function is:
function addStyles(target,css){
if(css == null ){
return target;
}
let props = Object.keys(css);
props.forEach((prop)=>{
switch(prop){
case 'id':
$(target).attr('id', css[prop]);
break;
case 'class':
let classes = css[prop];
if(Array.isArray(css[prop])){
classes = css[prop].toString().replace(',', ' ');
}
$(target).addClass(classes);
break;
default:
$(target).attr('data-name', 'Timi');
}
});
return target;
}
which for a long time gave me errors but only when calling it from the link function. I tried hard-coding in the href to see if maybe my string literals were giving me the errors but still no avail.
while calling it from this function it works perfectly
function createDiv(css){
let newDiv = $(div);
return addStyles(newDiv,css);
}
I say that the addStyles function works and that I think it's the link() that is giving me problems because createDiv works and appends the DOM with these calls
app.append(createDiv({id: 'testing'}));
app.append(createDiv({class: ['bar', 'snuff']}));
app.append(createDiv()).append(createDiv({class: 'timi'})).append(paragraph('Hey guys!'));
$('#testing').append(heading(1,'Hello'));
your let target = ; is a string. you should use a DOM and manipulate its attribute instead
function link(href,text,css){
let target = document.createElement('a');
let linkText = document.createTextNode(text);
target.appendChild(linkText);
target.href = href;
if(css == null ){
return target;
}
return addStyles(target,css);
}
You forgot to wrap the a tag with bling:
let target = $(``);
The reason your createDiv function is working as expected and your link function isn't is because you haven't wrapped your representation of an html element (the one you pass to addStyles) in a jQuery wrapper like you do in your createDiv function. See my below code for what I've changed to get it to work:
function link(href,text,css){
let target = `${text}`;
if(css == null ){
return target;
}
return addStyles($(target)[0],css);
}
codepen
I'm a little lost actually why we need to add [0] to $(target) but we do. Maybe someone else could chime in about why that is the case with ${text} and not with <div></div>
Edit: disregard above about the [0] and see comments on this answer.

How to get the nextSibling after the first nextSibling?

We are able to run this code
str.parentNode.nextSibling.tagName
and get the name accordingly. What we need is the nextSibling after this nextSibling so we tried this
str.parentNode.childNodes[3].tagName
is not working? Any solution to this?
childNodes[3] will return the fourth child of parentNode, not its second next sibling.
To get that node, you can apply nextSibling twice:
var tagName = str.parentNode.nextSibling.nextSibling.tagName;
Note that an element's nextSibling might be a text node, which doesn't have a tagName property, or may not exist at all, in which case nextSibling will be undefined and trying to access its tagName property will throw an error.
So you likely want do to something like:
var nextSibling = getNextElementSibling(str.parentNode);
var tagName = nextSibling? nextSibling.tagName : ''; // or maybe undefined
if (tagName) {
// do stuff
} else {
// no element sibling was found
}
and the getNextElementSibling function is something like:
function getNextElementSibling(node) {
while (node && (node = node.nextSibling)) {
if (node.nodeType == 1) {
return node;
}
}
// return undefined
}

How to handle Firefox inserting text elements between tags

I'm trying to write javascript to find page elements relative to a given element by using parentNode, firstChild, nextSibling, childNodes[], and so on. Firefox messes this up by inserting text nodes between each html element. I've read that I can defeat this by removing all whitespace between elements but I've tried that and it doesn't doesn't work. Is there a way to write code that works on all modern browsers?
For example:
<div id="parent"><p id="child">Hello world</p></div>
In IE parent.firstChild is child but in Firefix it's a phantom Text element.
I have a workaround. You can insert the two methods below:
Element.prototype.fChild = function(){
var firstChild = this.firstChild;
while(firstChild != null && firstChild.nodeType === 3){
firstChild = firstChild.nextSibling;
}
return firstChild;
}
Element.prototype.nSibling = function(){
var nextSibling = this.nextSibling;
while(nextSibling != null && nextSibling.nodeType === 3){
nextSibling = nextSibling.nextSibling;
}
return nextSibling;
}
and you can now use:
document.getElementById("parent").fChild();
document.getElementById("parent").nSibling();
instead of:
document.getElementById("parent").firstChild;
document.getElementById("parent").nextSibling;
You have to check that the nodeType == 1.
if (el.nodeType === 1) {
return el;
}
I wrote a small DOM traversing class for ya (mostly copied from MooTools).
Download here: http://gist.github.com/41440
DOM = function () {
function get(id) {
if (id && typeof id === 'string') {
id = document.getElementById(id);
}
return id || null;
}
function walk(element, tag, walk, start, all) {
var el = get(element)[start || walk], elements = all ? [] : null;
while (el) {
if (el.nodeType === 1 && (!tag || el.tagName.toLowerCase() === tag)) {
if (!all) {
return el;
}
elements.push(el);
}
el = el[walk];
}
return elements;
}
return {
// Get the element by its id
get: get,
walk: walk,
// Returns the previousSibling of the Element (excluding text nodes).
getPrevious: function (el, tag) {
return walk(el, tag, 'previousSibling');
},
// Like getPrevious, but returns a collection of all the matched previousSiblings.
getAllPrevious: function (el, tag) {
return walk(el, tag, 'previousSibling', null, true);
},
// As getPrevious, but tries to find the nextSibling (excluding text nodes).
getNext: function (el, tag) {
return walk(el, tag, 'nextSibling');
},
// Like getNext, but returns a collection of all the matched nextSiblings.
getAllNext: function (el, tag) {
return walk(el, tag, 'nextSibling', null, true);
},
// Works as getPrevious, but tries to find the firstChild (excluding text nodes).
getFirst: function (el, tag) {
return walk(el, tag, 'nextSibling', 'firstChild');
},
// Works as getPrevious, but tries to find the lastChild.
getLast: function (el, tag) {
return walk(el, tag, 'previousSibling', 'lastChild');
},
// Works as getPrevious, but tries to find the parentNode.
getParent: function (el, tag) {
return walk(el, tag, 'parentNode');
},
// Like getParent, but returns a collection of all the matched parentNodes up the tree.
getParents: function (el, tag) {
return walk(el, tag, 'parentNode', null, true);
},
// Returns all the Element's children (excluding text nodes).
getChildren: function (el, tag) {
return walk(el, tag, 'nextSibling', 'firstChild', true);
},
// Removes the Element from the DOM.
dispose: function (el) {
el = get(el);
return (el.parentNode) ? el.parentNode.removeChild(el) : el;
}
};
}();
// Now you can do:
DOM.getFirst("parent") // first child
// or
DOM.getFirst("parent", "p") // first p tag child
// or
var el = DOM.get("parent") // get element by id
DOM.getFirst(el) // first child
you could use tagName to check the name of the tag. If undefined then this is your 'phantom' text node.
e.g.
function getFirstTag(node) {
return ((node.firstChild.tagName) ? node.firstChild : node.firstChild.nextSibling);
}
Check the DOM level 2 core reference to see the various possible types of nodes, so you can filter out the undesired ones with a simple snippet of JavaScript. One solution is to monkey patch the object (see the answer of Vernicht) or if you do not like monkey patching, then you can add these methods to your own library, or an even better solution might be to use a fancy library like jQuery or prototype.

Categories

Resources