Nesta
Nesta

Reputation: 1008

Add or remove class only to the accordion panel that got clicked

I need some help on fixing a bug in my accordion component:

  1. In my example bellow please click on Panel 1 to open and click again on Panel 1 to close it.
  2. Then click on Panel 2. You will see that Panel 1 and Panel 2 get the class active at the same time and that's wrong. The class active should be added or removed only to the accordion panel that got clicked.

Note that I need to keep the same functionality which is accordion panel collapses when another one opens.

Your help and guidance would be much appreciated. Thank you.

var acc = document.getElementsByClassName("accordion");
var i;
var prevExpandedPanel = null;
for (i = 0; i < acc.length; i++) {
  acc[i].addEventListener("click", function() {
    if (prevExpandedPanel && this !== prevExpandedPanel) {  
      prevExpandedPanel.classList.toggle("active");
      prevExpandedPanel.nextElementSibling.style.maxHeight = null;
    }
    this.classList.toggle("active");
    var panel = this.nextElementSibling;
    if (panel.style.maxHeight) {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    } 
    prevExpandedPanel = this;
  });
}
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active, .accordion:hover {
  background-color: #f8f8f8;
}

.accordion:after {
  content: '\002B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

.panel {
  padding: 0 18px;
  background-color: white;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
<button class="accordion">Panel 1</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 2</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 3</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

Upvotes: 0

Views: 285

Answers (4)

Majid Parvin
Majid Parvin

Reputation: 5012

You are mixing the reference of this but assigning it like that.

Try this:

let acc = document.querySelectorAll('.accordion');
for (let i = 0; i < acc.length; i++) {
    acc[i].addEventListener("click", function() {
    for (let index = 0; index < acc.length; index++) {
      const el = acc[index];
      if (this !== el) {
        el.classList.remove('active');
        el.nextElementSibling.style.maxHeight = null;
      }
    }
    this.classList.toggle("active");
    let panel = this.nextElementSibling;
    if (panel.style.maxHeight) {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    } 
  });
}
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active, .accordion:hover {
  background-color: #f8f8f8;
}

.accordion:after {
  content: '\002B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

.panel {
  padding: 0 18px;
  background-color: white;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
<button class="accordion">Panel 1</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 2</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 3</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

Upvotes: 1

Ivan86
Ivan86

Reputation: 5708

You can do it without having to remember the previously selected element:

var acc = document.getElementsByClassName("accordion");

for (var i = 0; i < acc.length; i++) {
  acc[i].addEventListener("click", function(e) {
    let active = this.classList.contains('active');
    let panel = this.nextElementSibling;
    
    [...acc].forEach(function(item) {
      item.classList.remove('active');
      item.nextElementSibling.style.maxHeight = null;
    });
    
    if(!active) {
      this.classList.add('active');
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  });
}
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active, .accordion:hover {
  background-color: #f8f8f8;
}

.accordion:after {
  content: '\002B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

.panel {
  padding: 0 18px;
  background-color: white;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
<button class="accordion">Panel 1</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 2</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 3</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

Upvotes: 0

secan
secan

Reputation: 2679

A potential solution could be the following:

const accordionButtons = document.querySelectorAll(".accordion");

/* 
 * create an array of object associating the `button.accordion`
 * with the corresponding `div.panel`
 * (this is not strictly necessary, I just find it convenient)
 */
const accordions = []
accordionButtons.forEach(button => {
  accordions.push({
    btnEl: button,
    panelEl: button.nextElementSibling
  })
});

/*
 * create a function to handle the accordions toggle
 */
function expandSingle(accordion) {
  const isExpanded = accordion.btnEl.classList.contains('active');
  
  // 1. remove the active class from all accordions
  //    and set the max-height of all panels to 0
  accordions.forEach(item => {
    item.btnEl.classList.remove('active');
    item.panelEl.style.maxHeight = 0;
  });
  
  // 2. if the selected accordion was not already active/expanded
  //    add the active class to it and set the max-height
  if (!isExpanded) {
    accordion.btnEl.classList.add('active');
    accordion.panelEl.style.maxHeight = accordion.panelEl.scrollHeight + 'px';
  }
}

/*
 * add an event listener to each `button.accordion`, calling the function
 * which handles the accordions toggle
 */
accordions.forEach(accordion => {
  accordion.btnEl.addEventListener('click', () => expandSingle(accordion))
})
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active, .accordion:hover {
  background-color: #f8f8f8;
}

.accordion:after {
  content: '\002B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

.panel {
  padding: 0 18px;
  background-color: white;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
<button class="accordion">Panel 1</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 2</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<button class="accordion">Panel 3</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

Upvotes: 1

Roman Chasovitin
Roman Chasovitin

Reputation: 78

You need to wrap your accordions with the div:

<div class="accordion">
  <button class="button">Panel 1</button>
  <div class="panel">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
  </div>
</div>

<div class="accordion">
  <button class="button">Panel 2</button>
  <div class="panel">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
  </div>
</div>

<div class="accordion">
  <button class="button">Panel 3</button>
  <div class="panel">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
  </div>
</div>

After that just try the following code:

const resetCurrentActiveAccordion = (active) => {
    if(!active) return
  const buttonParent = active.closest('.accordion')
  const panel = buttonParent.querySelector('.panel')
  active.classList.remove('active')
  panel.style.maxHeight = null
}

const handleAccordionClick = event => {
    const parent = event.target.closest('.accordion')
  const panel = parent.querySelector('.panel')
  const active = document.querySelector('.active')
  
  resetCurrentActiveAccordion(active)
  if(active === event.target) return
  
  event.target.classList.add('active')
  panel.style.maxHeight = `${panel.scrollHeight}px`;
}

const initializeAccordion = () => {
  const accordions = document.querySelectorAll('.accordion')
  accordions.forEach(accordion => {
    const button = accordion.querySelector('.button')
    const panel = accordion.querySelector('.panel')
    button.addEventListener('click', handleAccordionClick)
  })
}

initializeAccordion()

And, of course, update your styles:

.button {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active, .button:hover {
  background-color: #f8f8f8;
}

.button:after {
  content: '\002B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

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

Code snippet link: https://jsfiddle.net/j9k8hdzb/

Upvotes: 0

Related Questions