I have a long list of list items (generated via an API) and I would like to show/hide some of the items with click, something like an accordion effect.
The list items that I would like to hide by default have P-item class, and the M-item class above them is what it should be triggering them to show.
Here is my markup:
<li class="session-item M-item"></li>
<li class="session-item P-item"></li>
<li class="session-item P-item"></li>
<li class="session-item G-item"></li>
P-items are children of their parent M-item. So by default in the above example, the two P-items should be hidden and M-item and G-item should be showing. When M-item is clicked, then the two hidden P-items show. I am only listing 4 items in the above example but the list goes on and the only separator between this relationship is the G-items.
I got it to work but it's only showing/hiding the first direct sibling of M-item, I used nextElementSibling, like this:
var acc = document.getElementsByClassName("M-item");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function () {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
What's the best way to select all siblings with the P-item and then break, looks for another M-item and repeat?
Is there something similar to nextAll(); in Vanilla JS? Do I need a while loop to iterate over the next sibling?
Here's how my full markup looks like:
My answer looks kinda like original, but uses a while loop to hide all following <li> tags as long as they have class .P-item. Loop stops if next element does not have that class, hence only hiding those P-items directly following the M-item that was pressed. Try it by copy pasting the whole code following here:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
.M-item {
background-color: red;
color: blue;
}
.active {
background-color: pink;
}
.hide {
display: none;
}
</style>
</head>
<body>
<ul class="agenda">
<li class="session-item M-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item M-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item M-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
<li class="session-item P-item">something</li>
</ul>
<script>
var acc = document.getElementsByClassName("M-item");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function () {
this.classList.toggle("active");
let next = this.nextElementSibling;
while (next.classList.contains('P-item')) {
next.classList.toggle('hide');
next = next.nextElementSibling;
}
});
}
</script>
</body>
</html>
I think the OP aims to find adjacent siblings with the same class.
One idea would be to intervene a little earlier, before the li's are setup as siblings, and impose an html structure there, grouping the like-classed siblings in div's (or ul's). But, if we're starting from the already built list, it can be handled as a reduce on the collection of li's...
const sessionItems = document.getElementsByClassName("session-item");
let groups = Array.from(sessionItems).reduce((acc, el, index) => {
const pItem = el.classList.contains("p-item")
const delimeter = !pItem || index===0;
if (delimeter) acc.push([]);
if (pItem) acc[acc.length-1].push(el);
return acc
}, []);
// now do whatever we want to the groups, like change visibility
// for clarity in this example, add a color class
groups.forEach((group, index) => {
group.forEach(el => el.classList.add(`color-${index}`))
});
.color-0 {
color: red;
}
.color-1 {
color: green;
}
<ul>
<li class="session-item m-item">item A</li>
<li class="session-item p-item">item B</li>
<li class="session-item p-item">item C</li>
<li class="session-item m-item">item D</li>
<li class="session-item p-item">item E</li>
<li class="session-item p-item">item F</li>
<li class="session-item p-item">item G</li>
<li class="session-item m-item">item H</li>
</ul>
You can maintain the visibility of the P items through CSS only, based on the "active" that the preceding M item has. This means the JS code only has to manage the toggling of the "active" class on the M item:
const ul = document.querySelector(".cad-agenda");
ul.addEventListener("click", function (e) {
const mItem = e.target;
if (!mItem.classList.contains("M-item")) return;
const wasActive = mItem.classList.contains("active");
document.querySelector(".active")?.classList?.remove("active");
mItem.classList.toggle("active", !wasActive);
});
.P-item { display: none }
.active ~ .P-item:not(.active ~ .M-item ~ .P-item) { display: block }
.M-item { cursor: pointer; font-weight: bold }
<ul class="cad-agenda accordion accordion-1">
<li class="session-item M-item">M-1</li>
<li class="session-item P-item">P-1.1</li>
<li class="session-item P-item">P-1.2</li>
<li class="session-item G-item">G-1</li>
<li class="session-item M-item">M-2</li>
<li class="session-item P-item">P-2.1</li>
<li class="session-item P-item">P-2.2</li>
<li class="session-item M-item">M-3</li>
<li class="session-item P-item">P-3.1</li>
<li class="session-item P-item">P-3.2</li>
<li class="session-item Press-item">PressItem</li>
<li class="session-item M-item">M-3</li>
<li class="session-item P-item">P-3.1</li>
<li class="session-item M-item">M-4</li>
<li class="session-item P-item">P-4.1</li>
<li class="session-item M-item">M-5</li>
<li class="session-item P-item">P-5.1</li>
<li class="session-item G-item">G-2</li>
<li class="session-item M-item">M-6 (has no P)</li>
<li class="session-item -item">empty 1</li>
<li class="session-item -item">empty 2</li>
<li class="session-item -item">empty 3</li>
<li class="session-item M-item">M-7 (has no P)</li>
<li class="session-item M-item">M-8</li>
<li class="session-item P-item">P-8.1</li>
<li class="session-item P-item">P-8.2</li>
</ul>