1

I want to create an accordion in my webshop's cart drawer but I can't find a way to fix my issue.

What is happening here is that I get the element but as soon as the drawer is re-rendered then my reference is lost. Because the DOM dynamically changes, I should not do it this way. Instead, I should use event delegation so that my code can still work even when things are re-rendered.

var acc = document.getElementsByClassName("accordion");
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";
    }
  });
}
<button type="button" class="accordion">Expand me</button>
<div class="panel">
  <p>Text here...</p>
</div>
Andy
  • 61,948
  • 13
  • 68
  • 95
Maurice
  • 11
  • 2

1 Answers1

0

Have an accordion container element that you attach the listener to. Then also cache the divs within each panel div. Use classes to specify whether each panel's information should be visible. Initially we set them all to display: none.

const accordion = document.querySelector('.accordion');
const add = document.querySelector('.add');

accordion.addEventListener('click', handleClick, false);
add.addEventListener('click', handleAdd, false);

function handleAdd() {
  const html = '<div class="panel"><button>Expand me: newPanel</button><div><p>New Panel</p></div></div>';
  accordion.insertAdjacentHTML('beforeend', html);
}

function handleClick(e) {
  const button = e.target;

  // Check to see if the element we clicked on was
  // the button
  if (button.nodeName === 'BUTTON') {

    // Find the closest panel ancestor
    const parent = button.closest('.panel');

    // Get all of the panels, even the ones newly added
    const panels = accordion.querySelectorAll('.panel div');

    // Remove all the `show` classes from the panels
    panels.forEach(panel => panel.classList.remove('show'));

    // And add `show` to the panels `div` which we previous hid
    parent.querySelector('div').classList.add('show');
  }
}
.panel div { display: none; }
.panel div.show { display: block; }
.add { margin-top: 1em; background-color: #44aa77; }
<div class="accordion">
  <div class="panel">
    <button>Expand me: one</button>
    <div><p>One</p></div>
  </div>
  <div class="panel">
    <button>Expand me: two</button>
    <div><p>Two</p></div>
  </div>
  <div class="panel">
    <button>Expand me: three</button>
    <div><p>Three</p></div>
   </div>
</div>
<button class="add">Add new panel</button>
Andy
  • 61,948
  • 13
  • 68
  • 95
  • Thank you for looking into this! It is still not working after replacing my code with yours. I've added type="button" to the buttons because it was redirecting to the cart page when I clicked it. After adding type="button", it doesn't do anything when clicking the text. Could it be an issue that it's located within a form? – Maurice Sep 23 '21 at 14:46
  • @Maurice, I've updated my answer so hopefully it better answers your question. If you're adding items then you need to specifically pick up all of the items in the handler. Just adjust your code to match your markup. I simplified it for this example but I hope you understand what I've done. – Andy Sep 23 '21 at 15:04