Creating Multiple Accordions html/Javascript/CSS

I am attempting to make nested accordions (you expand one, then inside you can expand another), but for some reason the inside accordion does not work.

This is based off of w3 school’s accordion code, but I replaced the buttons with an image. My logic was that if I copy paste the first accordion and rename it to “accordion 1,” I can just later call accordion1 and the same thing will happen as with the original accordion.

But when I click the inner accordion button, nothing happens. I’m not quite sure why this would be as it seems to be a direct copy paste. I’m not sure if something has to be done differently here

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.maxHeight) {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  });
}

var acc1 = document.getElementsByClassName("accordion1");
var i;

for (i = 0; i < acc1.length; i++) {
  acc1[i].addEventListener("click", function() {
    this.classList.toggle("active");
    var panel = this.nextElementSibling;
    if (panel.style.maxHeight) {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  });
}
.accordion {
  background-image: url(https://i.imgur.com/Jqsaukf.png);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  /* put the height and width of your image here */
  height: 50px;
  width: 200px;
  border: none;
  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: #ccc;
}

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

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

.accordion1 {
  background-image: url(https://i.imgur.com/VXlZ0Ja.png);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  /* put the height and width of your image here */
  height: 500px;
  width: 200px;
  border: none;
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}

.active1,
.accordion1:hover {
  background-color: #ccc;
}

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

.active1:after {
  content: "2212";
}

.panel {
  padding: 0 18px;
  background-color: white;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
<h2>Accordion with symbols</h2>
<p>In this example we have added a "plus" sign to each button. When the user clicks on the button, the "plus" sign is replaced with a "minus" sign.</p>

<button class="accordion"></button>
<div class="panel">
  <button class="accordion1"></button>
  <div class="panel1">
    <p>Lorem ipsum dolor sit amet</p>
  </div>
</div>
<br>
<button class="accordion"></button>
<div class="panel">
  <p><img src="https://i.imgur.com/Jqsaukf.png" alt="Italian Trulli"></p>
</div>

<button class="accordion"></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>

EDIT: I changed around variable names that I mixed up; i.e. panel and panel1 and active and active 1 and it works, but I still would like to know if there’s a more concise/efficient way to do this.

I’m going to want to have 10+ accordions with different images as buttons, so this module is going to end up being crazy long if there isn’t some way to condense it. Maybe a for loop that replaces every image in an array of accordions? Not sure if that makes sense or is even possible with html/javascript/css.

Answer

I made this example of how you could do this. The main thing is the looping concept and how you can keep the HTML simple for adding/deleting multiple accordion elements. You can nest as much as you like.

Notes: The .accordion element would make more sense being named .accordion-tab. I didn’t do much CSS styling, just for example purposes. In the Javascript, I showed how you can extract the image attribute and use it however you please. You probably could code a lot of this better and more efficiently, but I tried to make it quickly and readable to humans.

Let me know if you need any help. God bless!

const acc = document.querySelectorAll(".accordion"); // Get all accordion tab elements
let i;
for (i = 0; i < acc.length; i++) {
    const accordion = acc[i];
    accordion.setAttribute('data-index', i);

    // Add content
    const content = accordion.getAttribute('data-content'); // Getting the accordion tab content
    const accordionContent = document.createElement("div"); // Create content element
    accordionContent.classList.add("accordion-content"); //Add content class
    accordionContent.innerHTML += content; // Add HTML
    accordion.prepend(accordionContent) // Add to DOM, if you want the nested accordions before the content, you can use append instead of prepend


    // Accordion button
    const imageData = accordion.getAttribute('data-image') ? accordion.getAttribute('data-image').split('|') : false; // Get image and alt text then split it by the | character in the attribute
    const button = document.createElement("button"); // Create a button
    button.classList.add("accordion-button"); // Add button class
    button.style.backgroundImage = "url('" + imageData[0] + "')"; // Add background image, you could instead create an image element and add the imageData[0] to the src, but you'd probably need to create your images at the perfect size or add and extract the dimensions in the data-image attribute. Ex: data-image="image1.png|Alt text for image|100px,100px"

    button.setAttribute('aria-label', imageData[1]); // Add image alt text
    accordion.prepend(button); // Add to DOM
    button.onclick = function() {
        togglePanel(accordion); // Add button click function, function is below in the code
    }

}

function togglePanel(accordion) {
    const button = accordion.querySelector('.accordion-button')
    const accordionContent = accordion.querySelector('.accordion-content')
    
    // Handle toggling here. I suggest set CSS styles, but you could use javascript to slide it
    accordion.classList.toggle('active');

};
/* I used display none and block just for the example. You can do the height transition with CSS or Javascript */ 
.accordion .accordion,
.accordion .accordion-content {
    display: none;
}

.accordion.active>.accordion,
.accordion.active>.accordion-content {
    display: block;
}



/* Just for example styling*/
button {
  cursor: pointer;
  height: 50px;
  width: 100%;
}
button:after {
  content: '+'
}
.accordion.active > button:after {
  content: '-'
}
.accordion-content {
    padding: 20px;
  width: 100%;
}

.accordion .accordion {
  margin-left: 20px;
}
.accordion.active > button {
  background: #4169e1;
 }
.accordion .accordion.active > button {
  background: #a1caf1;
 }
 
 .accordion .accordion .accordion.active > button {
  background: #a4dded;
 }
<!-- Main accordion -->
<div class="accordion"
   data-image="image1.png|Alt text for image"
   data-content="Accordion 1 text"
   >
   <!-- Nested accordion -->
   <div class="accordion"
      data-image="image2.png|Alt text for image"
      data-content="Nested accordion 1 content. Image should show below: <img src=&quot;https://media.swncdn.com/via/11580-mount-zion-bst.jpg&quot; width=&quot;300&quot;>"
      ></div>
</div>


<div class="accordion"
   data-image="image2.png|Alt text for image"
   data-content="Accordion 2 text"
   >
   <!-- Nested accordion -->
   <div class="accordion"
      data-image="image2.png|Alt text for image"
      data-content="Nested accordion 2 text"
      ></div>
</div>

<div class="accordion"
   data-image="image3.png|Alt text for image"
   data-content="Accordion 3 has no nested accordion"
   >
</div>

<div class="accordion"
   data-image="image2.png|Alt text for image"
   data-content="Accordion 2 text"
   >
   <!-- Nested accordion -->
   <div class="accordion"
      data-image="image2a.png|Alt text for image"
      data-content="Nested accordion 2 text"
      >
    </div>
   <!-- Another nested accordion -->
   <div class="accordion"
      data-image="image2a.png|Alt text for image"
      data-content="Another nested accordion 2 text"
      >
         <!-- Double nested accordion -->
   <div class="accordion"
      data-image="image2b.png|Alt text for image"
      data-content="Double nested accordion 2 text"
      ></div>
      
      </div>
</div>