简体   繁体   中英

How to get all siblings of a parent item with specific classes in Vanilla JS

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM