Reputation: 473
I am trying to get the the first element with a specific class which follow the element clicked with pure JS (no JQuery) in the following way but get el.nextSibling is not a function error. Initially I was using JQuery parents().next() but would like to do this with pure JS:
const togglers = document.querySelectorAll('.toggler');
//console.log(togglers);
togglers.forEach(function(el) {
el.addEventListener('click', function(e) {
//const content = el.innerHTML;
//console.log(content);
el.nextSibling('.folder-content').style.display = 'block';
})
});
<div class="folder">
<div class="toggler">Click me 1</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
<div class="folder">
<div class="toggler">Click me 2</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
Any help would be appreciated :)
Upvotes: 3
Views: 22151
Reputation: 1
Nodes vs Elements In the HTML DOM terminology:
Nodes are all nodes (element nodes, text nodes, and comment nodes).
Whitespace between elements are also text nodes.
Elements are only element nodes.
Siblings vs Element Siblings Siblings are "brothers" and "sisters".
Siblings are nodes with the same parent (in the same childNodes list).
Element Siblings are elements with the same parent (in the same children list).
childNodes vs children childNodes returns child nodes (element nodes, text nodes, and comment nodes).
children returns child elements (not text and comment nodes).
nextSibling vs nextElementSibling nextSibling returns the next node (an element node, a text node or a comment node). Whitespace between elements are also text nodes.
nextElementSibling returns the next element (not text and comment nodes).
previousSibling vs previousElementSibling previousSibling returns the previous node (an element node, a text node or a comment node). Whitespace between elements are also text nodes.
previousElementSibling returns the previous element (not text and comment nodes).
Upvotes: 0
Reputation: 8660
I was building this answer while the others were coming in and, in the interest of variety and neat DOM programming, here's another way to skin this cat:
Here is the initial code that you have provided with a call to a separate function to do what was requested(find the next sibling matching a specified query string)
const togglers = document.querySelectorAll('.toggler');
togglers.forEach(function(el, i) {
el.addEventListener('click', function(e) {
searchNextSiblings(el, ".folder-content", function(ele) {
ele.style.display = "block";
});
})
});
You'll notice that the function I am providing is called searchNextSiblings
and it takes three parameters:
Here is the function itself:
function searchNextSiblings(ele, q, fn) {
let flag = false;
const nodeIterator = document.createNodeIterator(
ele.parentNode,
NodeFilter.SHOW_ELEMENT,
function(node) {
if (ele.isSameNode(node)) flag = true;
if (!flag) return NodeFilter.FILTER_REJECT;
else {
if (node.matches(q)) {
flag = false;
return NodeFilter.FILTER_ACCEPT
};
}
});
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
fn(currentNode);
}
}
And here is the annotated version:
function searchNextSiblings(ele, q, fn) {
// we want to search from the first Element provided
// to properly search we will set our crawler to begin
// from its parent node, and when we reach the first Element
// we will begin searching for the Query String
// in order to do this we declare a flag to False
// when we reach the first Element we will set it as True
// This will let us know when we can search for the Matching Query
let flag = false;
const nodeIterator = document.createNodeIterator(
ele.parentNode, //root to search from
NodeFilter.SHOW_ELEMENT, //set the iterator to search for elements
function(node) {
if (ele.isSameNode(node)) flag = true;
//if we've found the first Element, set the flag to True
if (!flag) return NodeFilter.FILTER_REJECT;
//if the flag is False, continue searching for first Element
else {
//if we have found the first Element,
//we are now searching for the Element that Matches the Query
if (node.matches(q)) {
//if we find a matching element
flag = false;
//set the flag to false to stop the search
return NodeFilter.FILTER_ACCEPT
//return the found node
};
}
});
// the above declares the node iterator
// but does not start it up
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
fn(currentNode);
}
//the above "starts up" the nodeIterator
}
const togglers = document.querySelectorAll('.toggler');
togglers.forEach(function(el, i) {
el.addEventListener('click', function(e) {
searchNextSiblings(el, ".folder-content", function(ele) {
ele.style.display = "block";
});
})
});
function searchNextSiblings(ele, q, fn) {
let flag = false;
const nodeIterator = document.createNodeIterator(
ele.parentNode,
NodeFilter.SHOW_ELEMENT,
function(node) {
if (ele.isSameNode(node)) flag = true;
if (!flag) return NodeFilter.FILTER_REJECT;
else {
if (node.matches(q)) {
flag = false;
return NodeFilter.FILTER_ACCEPT
};
}
});
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
fn(currentNode);
}
}
<div class="folder">
<div class="toggler">Click me 1</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
<div class="folder">
<div class="toggler">Click me 2</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
Upvotes: 2
Reputation: 1580
You can add a while
loop to go searching for the first nextSibling
that matches your criteria.
edit: As @j08691 points out, if your real code only cares about elements and not other node types, you can use nextElementSibling.
const togglers = document.querySelectorAll('.toggler');
//console.log(togglers);
togglers.forEach(function(el) {
el.addEventListener('click', function(e) {
var nextSibling = el.nextSibling;
while (nextSibling) {
if (
nextSibling.nodeType == Node.ELEMENT_NODE
&& nextSibling.classList.contains('folder-content')
) {
nextSibling.style.display = 'block';
break;
}
nextSibling = nextSibling.nextSibling;
}
})
});
<div class="folder">
<div class="toggler">Click me 1</div>
<div>not this</div>
<div>not this</div>
<div class="folder-content" style="display: none">
Lorem ipsum 1!
</div>
<div>not this</div>
<div class="toggler">Click me 1.5</div>
<div>not this</div>
<div class="folder-content" style="display: none">
Lorem ipsum 1.5!
</div>
<div>not this</div>
<div>not this</div>
</div>
<div class="folder">
<div class="toggler">Click me 2</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
<div class="folder">
<div class="toggler">Click me 3</div>
<div>no folder content here!</div>
</div>
Upvotes: 2
Reputation: 207901
nextElement
is a property, not a function, so you don't use ()
with it. That said, using nextSibling
can give you white space content which you don't want. Instead you can use nextElementSibling
:
el.nextElementSibling.style.display = 'block';
const togglers = document.querySelectorAll('.toggler');
//console.log(togglers);
togglers.forEach(function(el) {
el.addEventListener('click', function(e) {
//const content = el.innerHTML;
//console.log(content);
el.nextElementSibling.style.display = 'block';
})
});
<div class="folder">
<div class="toggler">Click me 1</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
<div class="folder">
<div class="toggler">Click me 2</div>
<div class="folder-content" style="display: none">
Lorem ipsum
</div>
</div>
Upvotes: 12