0

I have built an accordion which I can add dynamically from an input and everything works fine except when I click on accordion heading text it doesn't work and also when I click on the chevron icon on the right side I get an error!! I am not sure why this happening. if I click on an empty space area it just works fine without any error. you can check the demo & code here on codepen -> https://codepen.io/tauhidul-islam/pen/eYZBzLY Also here is some screenshot so you can understand. please let me understand what's happening and why. Thank you.

const addForm = document.querySelector(".add");
const list = document.querySelector(".section-list");

// Template Generator Function
const generateTemplate = (section) => {
  let html = `
     <div class="accordion">
        <span>${section}</span>
        <i class="fa fa-chevron-down"></i>
     </div>
     <div class="panel">
       <span>Hey there you did it! :-)</span>
     </div>
    `;
  list.innerHTML += html;

  // accordion Selector
  const accordion = document.querySelectorAll(".accordion");

  // Show/Hide accordion Content on Click
  for (i = 0; i < accordion.length; i++) {
    accordion[i].addEventListener("click", (e) => {
      let panel = e.target.nextElementSibling;

      if (panel.classList.contains("panel")) {
        panel.classList.toggle("active");
      }
    });
  }
};

// Add Section
addForm.addEventListener("submit", (e) => {
  e.preventDefault();
  const section = addForm.add.value.trim();
  if (section.length) {
    generateTemplate(section);
    addForm.reset();
  }
});
.container {
  width: 960px;
  margin: auto;
}

.add-input {
  padding: 15px;
  border: 1px solid #dadada;
}

.add-btn {
  background: white;
  padding: 15px 25px;
  margin-bottom: 10px;
  border: 1px solid #dadada;
  cursor: pointer;
}

/* Accordian Panel */

.accordion {
  display: flex;
  justify-content: space-between;
  background: #03a9f4;
  color: white;
  padding: 15px;
  box-shadow: 0px 0px 4px 0px #dadada;
  cursor: pointer;
}

.panel {
  display: none;
  background-color: white;
  padding: 15px;
}

.active {
  display: block;
}
<div class="container">
    <!-- Add Section -->
    <form class="add">
      <input type="text" name="add" class="add-input">
      <button type="submit" class="add-btn">Add Section</button>
    </form>

    <!-- Section List -->
    <div class="section-list"></div>
</div>

enter image description here

enter image description here

enter image description here

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
Tauhid
  • 37
  • 7
  • The error only pops up when you click on a child element? If so, this is happening because when you click on a child element, it is looking for the child's sibling, not the sibling to its parent element. Have you tried event.currentTarget.nextElementSibling instead of event.target.nextElementSibling? Here's an explainer: https://stackoverflow.com/questions/10086427/what-is-the-exact-difference-between-currenttarget-property-and-target-property – rguttersohn Aug 20 '20 at 22:25

2 Answers2

0

Because you are using e.target in the click event of the generated div, that will reference the template span when you click on the text and the div when you click on the blue bar, so .nextElementSibling won't always point to the same element. Instead, you want to always be calling .nextElementSibling on the div. This can be accomplished by using this.nextElementSibling, however because you are also using an arrow function, this binding won't correctly reference the element that received the event (the div), so if you change to using an anonymous function and this, it works.

const addForm = document.querySelector(".add");
const list = document.querySelector(".section-list");

// Template Generator Function
const generateTemplate = (section) => {
  let html = `
     <div class="accordion">
        <span>${section}</span>
        <i class="fa fa-chevron-down">^</i>
     </div>
     <div class="panel">
       <span>Hey there you did it! :-)</span>
     </div>
    `;
  list.innerHTML += html;

  // accordion Selector
  const accordion = document.querySelectorAll(".accordion");

  // Show/Hide accordion Content on Click
  for (i = 0; i < accordion.length; i++) {
    // Use an  anonymous function for the event listener so that 
    // "this" will bind to the element that recieved the event,
    // which is the `div` in this case.
    accordion[i].addEventListener("click", function(e) {
      // We don't want to reference the element that triggered the event
      // because that might be the span or the div and you won't always get
      // the correct reference with .nextElementSibling. We always want to
      // start from the div, which recieves the event.
      let panel = this.nextElementSibling;
      if (panel.classList.contains("panel")) {
        panel.classList.toggle("active");
      }
    });
  }
};

// Add Section
addForm.addEventListener("submit", (e) => {
  e.preventDefault();
  const section = addForm.add.value.trim();
  if (section.length) {
    generateTemplate(section);
    addForm.reset();
  }
});
.container {
  width: 960px;
  margin: auto;
}

.add-input {
  padding: 15px;
  border: 1px solid #dadada;
}

.add-btn {
  background: white;
  padding: 15px 25px;
  margin-bottom: 10px;
  border: 1px solid #dadada;
  cursor: pointer;
}

/* Accordian Panel */

.accordion {
  display: flex;
  justify-content: space-between;
  background: #03a9f4;
  color: white;
  padding: 15px;
  box-shadow: 0px 0px 4px 0px #dadada;
  cursor: pointer;
}

.panel {
  display: none;
  background-color: white;
  padding: 15px;
}

.active {
  display: block;
}
<div class="container">
    <!-- Add Section -->
    <form class="add">
      <input type="text" name="add" class="add-input">
      <button type="submit" class="add-btn">Add Section</button>
    </form>

    <!-- Section List -->
    <div class="section-list"></div>
</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
0

Without the loop for assigning the click handlers:

const addForm = document.querySelector(".add");
const list = document.querySelector(".section-list");
const expand = (element) => {
  let panel = element.nextElementSibling;
  if (panel.classList.contains("panel")) {
    panel.classList.toggle("active");
  }
};


// Template Generator Function
const getAccordionItem = (section) => {
  let html = `
      <div class="accordion" onclick="expand(this)">
                <span>${section}</span>
                <i class="fa fa-chevron-down"></i>
            </div>
            <div class="panel">
                <span>Hey there you did it! :-)</span>
            </div>
    `;
  return html;
};

// Add Section
addForm.addEventListener("submit", (e) => {
  e.preventDefault();
  const section = addForm.add.value.trim();
  if (section.length) {
      list.innerHTML += getAccordionItem(section);
    addForm.reset();
  }
});
body {
  margin: 50px 0;
  background-color: #f2f2f2;
  font-family: Arial, Helvetica, sans-serif;
}

.container {
  width: 960px;
  margin: auto;
}

.add-input {
  padding: 15px;
  border: 1px solid #dadada;
}

.add-btn {
  background: white;
  padding: 15px 25px;
  margin-bottom: 10px;
  border: 1px solid #dadada;
  cursor: pointer;
}

/* Accordian Panel */

.accordion {
  display: flex;
  justify-content: space-between;
  background: #03a9f4;
  color: white;
  padding: 15px;
  box-shadow: 0px 0px 4px 0px #dadada;
  cursor: pointer;
}

.panel {
  display: none;
  background-color: white;
  padding: 15px;
}

.active {
  display: block;
}
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dynamic Accordian</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css">
  <link rel="stylesheet" href="css/style.css">
</head>

<body>

  <div class="container">
    <!-- Add Section -->
    <form class="add">
      <input type="text" name="add" class="add-input">
      <button type="submit" class="add-btn">Add Section</button>
    </form>

    <!-- Section List -->
    <div class="section-list"></div>
  </div>

  <script src="app.js"></script>
</body>

</html> 
Arthur Rubens
  • 4,626
  • 1
  • 8
  • 11