Greg
Greg

Reputation: 473

Get next element with specific class of clicked element with pure js

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

Answers (4)

user25209271
user25209271

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

zfrisch
zfrisch

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:

  1. The Element that you would like to search from.
  2. The Query String you would like the found element to match.
  3. A function that receives the found element, and that you can use to manipulate it.

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

Becca
Becca

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

j08691
j08691

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

Related Questions