Hide an element's next sibling with Javascript - javascript

I have an element grabbed from document.getElementById('the_id'). How can I get its next sibling and hide it? I tried this but it didn't work:
elem.nextSibling.style.display = 'none';
Firebug error was elem.nextSibling.style is undefined.

it's because Firefox considers the whitespace between element nodes to be text nodes (whereas IE does not) and therefore using .nextSibling on an element gets that text node in Firefox.
It's useful to have a function to use to get the next element node. Something like this
/*
Credit to John Resig for this function
taken from Pro JavaScript techniques
*/
function next(elem) {
do {
elem = elem.nextSibling;
} while (elem && elem.nodeType !== 1);
return elem;
}
then you can do
var elem = document.getElementById('the_id');
var nextElem = next(elem);
if (nextElem)
nextElem.style.display = 'none';

Take a look at the Element Traversal API, that API moves between Element nodes only. This Allows the following:
elem.nextElementSibling.style.display = 'none';
And thus avoids the problem inherent in nextSibling of potentially getting non-Element nodes (e.g. TextNode holding whitespace)

Firebug error was elem.nextSibling.style is undefined.
because nextSibling can be a text-node or other node type
do {
elem = elem.nextSibling;
} while(element && elem.nodeType !== 1); // 1 == Node.ELEMENT_NODE
if(elem) elem.style.display = 'none';

Try looping through the children of this element using something like:
var i=0;
(foreach child in elem)
{
if (i==0)
{
document.getElementByID(child.id).style.display='none';
}
}
Please make appropriate corrections to the syntax.

Related

How ot select an element in the DOM hierarchy that has a specific class? [duplicate]

How can I find an element's ancestor that is closest up the tree that has a particular class, in pure JavaScript? For example, in a tree like so:
<div class="far ancestor">
<div class="near ancestor">
<p>Where am I?</p>
</div>
</div>
Then I want div.near.ancestor if I try this on the p and search for ancestor.
Update: Now supported in most major browsers
document.querySelector("p").closest(".near.ancestor")
Note that this can match selectors, not just classes
https://developer.mozilla.org/en-US/docs/Web/API/Element.closest
For legacy browsers that do not support closest() but have matches() one can build selector-matching similar to #rvighne's class matching:
function findAncestor (el, sel) {
while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
return el;
}
This does the trick:
function findAncestor (el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
The while loop waits until el has the desired class, and it sets el to el's parent every iteration so in the end, you have the ancestor with that class or null.
Here's a fiddle, if anyone wants to improve it. It won't work on old browsers (i.e. IE); see this compatibility table for classList. parentElement is used here because parentNode would involve more work to make sure that the node is an element.
Use element.closest()
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
See this example DOM:
<article>
<div id="div-01">Here is div-01
<div id="div-02">Here is div-02
<div id="div-03">Here is div-03</div>
</div>
</div>
</article>
This is how you would use element.closest:
var el = document.getElementById('div-03');
var r1 = el.closest("#div-02");
// returns the element with the id=div-02
var r2 = el.closest("div div");
// returns the closest ancestor which is a div in div, here is div-03 itself
var r3 = el.closest("article > div");
// returns the closest ancestor which is a div and has a parent article, here is div-01
var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article
Based on the the8472 answer and https://developer.mozilla.org/en-US/docs/Web/API/Element/matches here is cross-platform 2017 solution:
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
function findAncestor(el, sel) {
if (typeof el.closest === 'function') {
return el.closest(sel) || null;
}
while (el) {
if (el.matches(sel)) {
return el;
}
el = el.parentElement;
}
return null;
}
#rvighne solution works well, but as identified in the comments ParentElement and ClassList both have compatibility issues. To make it more compatible, I have used:
function findAncestor (el, cls) {
while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
return el;
}
parentNode property instead of the parentElement property
indexOf method on the className property instead of the contains method on the classList property.
Of course, indexOf is simply looking for the presence of that string, it does not care if it is the whole string or not. So if you had another element with class 'ancestor-type' it would still return as having found 'ancestor', if this is a problem for you, perhaps you can use regexp to find an exact match.
This solution should work for IE9 and up.
It's like jQuery's parents() method when you need to get a parent container which might be up a few levels from the given element, like finding the containing <form> of a clicked <button>. Looks through the parents until the matching selector is found, or until it reaches the <body>. Returns either the matching element or the <body>.
function parents(el, selector){
var parent_container = el;
do {
parent_container = parent_container.parentNode;
}
while( !parent_container.matches(selector) && parent_container !== document.body );
return parent_container;
}

Scan the DOM upward to find element matching selector (no jQuery)

I wanted a function that could scan the DOM upward from a DOMElement and also scan the children of each parent as it's going up.
It had to keep going until it would found any <element> matching the selector received in parameter. The selector had to be any type of valid CSS selector.
It was also needed to be done in pure JS (no jQuery)
I ended up making a modified version of this function that I found on this site. You can use this function as you wish, scale it up, claim it yours, whatever you want.
Here's the solution I found
GetClosest = function (elem, selector) {
for (; elem && elem !== document.body; elem = elem.parentNode) {
// If the elem matches at first iteration.
if(elem.matches(selector)) {
return elem;
} else {
// Scans all the childs of current iterated element (always higher in DOM until found).
// If one matches the selector it'll stop and return it.
child = elem.parentNode.firstChild;
do {
if(child.nodeType === 3) continue; // text node
if(child.matches(selector)) return child;
} while (child = child.nextElementSibling);
}
}
return null;
};

Find the closest ancestor element that has a specific class

How can I find an element's ancestor that is closest up the tree that has a particular class, in pure JavaScript? For example, in a tree like so:
<div class="far ancestor">
<div class="near ancestor">
<p>Where am I?</p>
</div>
</div>
Then I want div.near.ancestor if I try this on the p and search for ancestor.
Update: Now supported in most major browsers
document.querySelector("p").closest(".near.ancestor")
Note that this can match selectors, not just classes
https://developer.mozilla.org/en-US/docs/Web/API/Element.closest
For legacy browsers that do not support closest() but have matches() one can build selector-matching similar to #rvighne's class matching:
function findAncestor (el, sel) {
while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
return el;
}
This does the trick:
function findAncestor (el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
The while loop waits until el has the desired class, and it sets el to el's parent every iteration so in the end, you have the ancestor with that class or null.
Here's a fiddle, if anyone wants to improve it. It won't work on old browsers (i.e. IE); see this compatibility table for classList. parentElement is used here because parentNode would involve more work to make sure that the node is an element.
Use element.closest()
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
See this example DOM:
<article>
<div id="div-01">Here is div-01
<div id="div-02">Here is div-02
<div id="div-03">Here is div-03</div>
</div>
</div>
</article>
This is how you would use element.closest:
var el = document.getElementById('div-03');
var r1 = el.closest("#div-02");
// returns the element with the id=div-02
var r2 = el.closest("div div");
// returns the closest ancestor which is a div in div, here is div-03 itself
var r3 = el.closest("article > div");
// returns the closest ancestor which is a div and has a parent article, here is div-01
var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article
Based on the the8472 answer and https://developer.mozilla.org/en-US/docs/Web/API/Element/matches here is cross-platform 2017 solution:
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
function findAncestor(el, sel) {
if (typeof el.closest === 'function') {
return el.closest(sel) || null;
}
while (el) {
if (el.matches(sel)) {
return el;
}
el = el.parentElement;
}
return null;
}
#rvighne solution works well, but as identified in the comments ParentElement and ClassList both have compatibility issues. To make it more compatible, I have used:
function findAncestor (el, cls) {
while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
return el;
}
parentNode property instead of the parentElement property
indexOf method on the className property instead of the contains method on the classList property.
Of course, indexOf is simply looking for the presence of that string, it does not care if it is the whole string or not. So if you had another element with class 'ancestor-type' it would still return as having found 'ancestor', if this is a problem for you, perhaps you can use regexp to find an exact match.
This solution should work for IE9 and up.
It's like jQuery's parents() method when you need to get a parent container which might be up a few levels from the given element, like finding the containing <form> of a clicked <button>. Looks through the parents until the matching selector is found, or until it reaches the <body>. Returns either the matching element or the <body>.
function parents(el, selector){
var parent_container = el;
do {
parent_container = parent_container.parentNode;
}
while( !parent_container.matches(selector) && parent_container !== document.body );
return parent_container;
}

What are valid node types for the Range setEndAfter function in Safari?

I am writing a module that should allow users to select parts of an HTML document. To get the internals to work I expand the Range of the selection to a valid HTML snippet.
For the case where B is a descendant of A I find the ancestor of B which is a child of A and want to set the range to end after that node using setEndAfter. This is what I have now:
var closestChild = function (node, descendant) {
var parent;
if (descendant.parentElement) {
parent = descendant.parentElement;
if ( node === parent ) {
return descendant;
}
return closestChild(node, parent);
}
return false;
}
var legalRange = function (range) {
var newRange = range.cloneRange(),
child;
if (range.startContainer === range.endContainer) {
return newRange;
}
child = closestChild(range.startContainer.parentElement, range.endContainer.parentElement);
if (child) {
newRange.setEndAfter(child);
return newRange;
}
return null;
};
But this throws a INVALID_NODE_TYPE_ERR: DOM Range Exception 2 when I try to set the end point. I have also tried using parentNode instead of parentElement with the same exception thrown. This is not a problem if i use setEnd(). What types of nodes should I pass to do this.
PS: It turns out that the code works in FireFox, so my problem is now with Safari and Chrome.
I found the solution.
When I set up my test cases, I didn't add the elements to the document. It seems that Chrome and Safari treated the nodes as invalid when using setEndAfter if the nodes were not part of the document.
As answered by Eivind, the problem is that the node used to set the range position is not attached to the document. So one solution is to attach it before you set the range position.
Another solution, if you can't attach the node to the DOM for some reason, is to use setStart() and setEnd() instead.
// Instead of `range.setStartBefore(node)`
var parent = node.parent;
range.setStart(parent, Array.from(parent.childNodes).indexOf(node))
// Instead of `range.setStartAfter(node)`
var parent = node.parent;
range.setStart(parent, Array.from(parent.childNodes).indexOf(node) + 1)
// Instead of `range.setEndBefore(node)`
var parent = node.parent;
range.setEnd(parent, Array.from(parent.childNodes).indexOf(node))
// Instead of `range.setEndAfter(node)`
var parent = node.parent;
range.setEnd(parent, Array.from(parent.childNodes).indexOf(node) + 1)
Note: Array.from(arrayLike) is not supported in Internet Explorer <= 11. Use Array.prototype.slice.call(arrayLike) instead of you need IE support.

Splitting node content in JavaScript DOM

I have a scenario where I need to split a node up to a given ancestor, e.g.
<strong>hi there, how <em>are <span>you</span> doing</em> today?</strong>
needs to be split into:
<strong>hi there, how <em>are <span>y</span></em></strong>
and
<strong><em><span>ou</span> doing</em> today?</strong>
How would I go about doing this?
Here is a solution that will work for modern browsers using Range. Something similar could be done for IE < 9 using TextRange, but I use Linux so I don't have easy access to those browsers. I wasn't sure what you wanted the function to do, return the nodes or just do a replace inline. I just took a guess and did the replace inline.
function splitNode(node, offset, limit) {
var parent = limit.parentNode;
var parentOffset = getNodeIndex(parent, limit);
var doc = node.ownerDocument;
var leftRange = doc.createRange();
leftRange.setStart(parent, parentOffset);
leftRange.setEnd(node, offset);
var left = leftRange.extractContents();
parent.insertBefore(left, limit);
}
function getNodeIndex(parent, node) {
var index = parent.childNodes.length;
while (index--) {
if (node === parent.childNodes[index]) {
break;
}
}
return index;
}
Demo: jsbin
It expects a TextNode for node, although it will work with an Element; the offset will just function differently based on the behavior of Range.setStart
See the method Text.splitText.
Not sure if this helps you, but this is what I came up with...
Pass the function an element and a node tag name string you wish to move up to.
<strong>hi there, how <em>are <span id="span">you</span> doing</em> today?</strong>
<script type="text/javascript">
function findParentNode(element,tagName){
tagName = tagName.toUpperCase();
var parentNode = element.parentNode;
if (parentNode.tagName == tagName){
//Erase data up to and including the node name we passed
console.log('Removing node: '+parentNode.tagName+' DATA: '+parentNode.firstChild.data);
parentNode.firstChild.data = '';
return parentNode;
}
else{
console.log('Removing node: '+parentNode.tagName+' DATA: '+parentNode.firstChild.data);
//Erase the first child's data (the first text node and leave the other nodes intact)
parentNode.firstChild.data = '';
//Move up chain of parents to find the tag we want. Return the results so we can do things with it after
return findParentNode(parentNode, tagName)
}
}
var ourNode = document.getElementById("span");
alert(findParentNode(ourNode,'strong').innerHTML);
</script>

Categories

Resources