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>
From css set display: none;
of all elements with class P-item
An event listener is placed on all items with the M-item
class.
When an item with class M-item
is clicked, the function myFunction()
is called.
Step 1: Removes all active
classes.
Step 2: Loop through all the items until it reaches the clicked item. When it reaches the clicked element of the variable flag
, the value is set to true
. The active
class is added to the clicked element and to all subsequent elements with class P-item
. The loop breaks when it reaches an element with a different class from P-item
const wrap = document.querySelector('.wrap'); const listAll = wrap.querySelectorAll('li'); const listM = wrap.querySelectorAll('.M-item'); listM.forEach(el => { el.addEventListener('click', myFunction) }); function myFunction() { listAll.forEach(el => { el.classList.remove('active') }); let flag = false; for (let el of listAll) { if (el === this) { flag = true; } if (flag) { if (el === this || el.classList.contains('P-item')) { el.classList.add('active'); } else { break; } } } };
.M-item { cursor: pointer; }.P-item { display: none; }.active { display: block; }
<ul class="wrap"> <li class="session-item M-item">M-item Click me</li> <li class="session-item P-item">P-item</li> <li class="session-item P-item">P-item</li> <li class="session-item G-item">G-item</li> <li class="session-item M-item">M-item Click me</li> <li class="session-item P-item">P-item</li> <li class="session-item P-item">P-item</li> <li class="session-item G-item">G-item</li> </ul>
Late arrival. I've used a data attribute to link parent to child.
let count = 0;
document
querySelectorAll("[class~=session-item]")
.forEach((s)=>
{
if (s.classList.contains("M-item")) {
s.addEventListener("click", function () {
this.classList.toggle("active");
document
.querySelectorAll(`[data-id='C${this.getAttribute("data-id")}']`)
.forEach( (p) => p.classList.toggle("hide") );
});
s.setAttribute("data-id", ++count);
}
else
{
s.setAttribute("data-id", `C${count}`);
}
});
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.