简体   繁体   中英

Creating a toggle button for expanding and collapsing an Accordion

I created a modified accordion for a FAQ page I am working on from a W3Schools example. I am new to Javascript and I am in need of a bit of help. I wanted to build a button that would toggle between expanding all of the panels and closing all the panels. I was able to get some help from another blog post that someone had written to create a close all panels button but now I would like to build a function that will toggle between closing all and opening all. I am also looking to have all of this inline so that I can embed this one page of code into another page. I have attached the code that I have created so far.

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
.accordion {
    background-color: none;
    cursor: pointer;
    padding: 30px;
    width: 100%;
    border: #DADCE0;
    border-style: solid;
    border-width: 1px 0px 0px 0px;
    text-align: left;
    outline: none;
    font-family: Google Sans, sans-serif;
    font-weight: medium;
    font-size: 18px;
    line-height: 30px;
    transition: 0.4s;
    color: #1A73E8;
}

.accordion:hover {
    color: #174EA6;
}

.active {
    color: #174EA6;
    border-top-color: #DADCE0;
    border-bottom-color: #174EA6;
    border-style: solid;
    border-width: 1px 0px 2px 0px;
}

.accordion:after {
    font-family: 'Material Icons';
    content: "keyboard_arrow_down"; /*Google keyboard arrow down*/
    font-weight: bold;
    font-size: 24px;
    float: right;
    margin-left: 5px;
}

.active:after {
  font-family: 'Material Icons';
  content: "keyboard_arrow_up"; /*Google keyboard arrow up*/
  font-weight: bold;
  font-size: 24px;
  float: right;
  margin-left: 5px;
}

.panel {
    padding: 0px 18px;
    background-color: white;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s ease-out;
}

.closeall {
    float: right;
    margin: 1% 2% 0 0;
    cursor: pointer;
}

body {
    font-family: Roboto Light, sans-serif;
    line-height: 26px;
    font-size: 16px;
    color: #202124;
}

a {
    color: #4285f4;
}

</style>
</head>
<body>

<button  class="closeall" onclick="collapseall()">Close all</button>

<button class="accordion">Can I host my own events?</button>
<div class="panel">
  <p>Of course you can!</p>
</div>

<button class="accordion">Are the events for students?</button>
<div class="panel">
  <p>Yes, please see here.</p>
</div>

<button class="accordion">Are the events for teachers?</button>
<div class="panel">
  <p>Yes, please see here.</p>
</div>

<button class="accordion">Are the events for teachers 2?</button>
<div class="panel">
  <p>Yes, please see here 2.</p>
</div>

<script>
var acc = document.getElementsByClassName("accordion");
var i;

for (i = 0; i < acc.length; i++) {
  acc[i].onclick = function() {
    this.classList.toggle("active");
    var panel = this.nextElementSibling;
    if (panel.style.maxHeight){
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  }
}

function collapseall() {  //problematic part
    var x = document.getElementsByClassName("panel");
    var b;
    for (b = 0; b < x.length; b++) {
        x[b].style.maxHeight = null;
        x[b].previousElementSibling.classList.remove('active');
  }
}
</script>

</body>
</html>
function collapseall() {
    var x = document.getElementsByClassName("accordion");
    var y = 0;
    var b;

    for (b = 0; b < x.length; b++) {
       if(x[b].classList.contains('active'))y++;
    }

    if(x.length==y||y==0){
      for (b = 0; b < x.length; b++) {
         x[b].dispatchEvent(new Event('click'));
      }
    }else{
      for (b = 0; b < x.length; b++) {
         if(x[b].classList.contains('active'))
            x[b].dispatchEvent(new Event('click'));
      }
    }
}

Good news is you're very close to a proper solution, which I implemented here for you. Short way to achieve that would might look like

// Single reference to all panels 
// Getting them each time is heavy for the browser
var x = document.getElementsByClassName("panel");

// Indicator if we should open or close all panels
var isToggledOff = true;

Then you need two very similar functions to open or collapse all panels

function collapseAll() { 
    for (var b = 0; b < x.length; b++) {
        x[b].style.maxHeight = null;
        x[b].previousElementSibling.classList.remove('active');
  }
}

function expandAll() {
    for (var b = 0; b < x.length; b++) {
        x[b].style.maxHeight = "58px";
        x[b].previousElementSibling.classList.add('active');
   }
}

Last but not least you need logic that will decide if we want to open or close all panels and what to do with them in the future

function toggle() {
   if (isToggledOff) this.expandAll();
   else this.collapseAll();
   isToggledOff = !isToggledOff
}

Additional things you might want to consider

  1. Why do you need to set that maxHeight? Wouldn't it be easier to add it to active CSS class?
  2. Functional programming in JS gives you super powers, why wouldn't you use .forEach() method instead of for loop? It might be tricky but worthy lesson :)
  3. Why do you need previousElementSibling property?
  4. Next time you add a question try to provide people helping you with minimal example, so that they can easier understand what's the problem. In this case you could strip css code from your example :)

Alternative way

var x = Array.from(document.getElementsByClassName("panel"));
var isToggledOff = true;
function toggle() {
    x.forEach(el => el
        .previousElementSibling
        .classList
        [isToggledOff ? "add" : "remove"]('active')
    )
   isToggledOff = !isToggledOff
}

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