I've tried a few different ways but can not tell why my.JS isn't opening and closing my accordion. Please check my code. Your help would be appreciated.
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";
}
});
}
/*this javascript seems to be fine and matches what some answers have said, and i've reset the page cache but i cannot seem to get it to work */
body {
text-align: center;
background-color: #ffffff;
}
h1 {
font: 40px courier new;
text-align: center;
}
a {
font: 25px arial;
text-align:center;
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 60%;
text-align: center;
border: none;
outline: none;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
overflow: hidden;
font: 16px Source Sans Pro;
width:60%;
margin:0 auto;
display:none;
height:200px auto
}
p{
text-align:center;
}
<div class="wrap1">
<button class="accordion">About Us Page Map</button>
<div id="pan1" class="panel">
<p>
Personel section<br />
Acheivments<br />
History<br />
</p>
</div>
</div>
I've added the css, i do have a class called accordion but it didn't show in the original question sorry!
The problem is here:
var acc = document.getElementsByClassName("accordion");
You don't have "accordion" class in your HTML file, that's why it is not able to recognize it. You have to take a button, assign it a class named accordion and then it will work properly.
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";
}
});
}
.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: #ccc;
}
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
<button class="accordion">Personal</button>
<div class="panel">
<p>Personal here</p>
</div>
<button class="accordion">Achievements</button>
<div class="panel">
<p>Achievements here</p>
</div>
<button class="accordion">History</button>
<div class="panel">
<p>History here</p>
</div>
Source: HTML and CSS Accordion - w3schools
Hope this helps.
I would suggest refactoring some of your code to make it more readable such as
HTML
Item 1
<div id="item1" class="accordion-content">This is my accordion Item 1 content</div>
Item 2
<div id="item2" class="accordion-content">This is my accordion Item 2 content</div>
JS
document.querySelectorAll(".accordion").forEach(function(node) {
node.addEventListener("click", function(e) {
this.nextElementSibling.toggleClass("active');
});
});
CSS
.accordion-content:not(.active) {
display: none;
}
Besides the refactor, I'm unable to see an element with the className accordion. It seems you are missing the HTML containing the actual accordion.
Related
I have an accordion where the panels are pseudo buttons that expand the accordion content once the "buttons" are clicked and the currently open accordion panel closes once I click on another one. The problem I have is that if I click on the panel once to open the accordion and click on it again to close it, it stays focused (the orange border remains) and I cannot figure out how to bring the button back to the state it was in before I clicked it? In the same way it is reset when I click on another panel?
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
var panel = this.nextElementSibling;
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
let active = document.querySelectorAll(".accordion-div .accordion.active");
for (let j = 0; j < active.length; j++) {
active[j].classList.remove("active");
active[j].nextElementSibling.style.maxHeight = null;
}
this.classList.toggle("active");
panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}
.accordion {
color: #fff;
background-color: #00000042;
cursor: pointer;
padding: 10px 10px;
margin-top: 20px;
width: 100%;
border: 1px solid #00000042;
text-align: left;
outline: none;
transition: 0.4s;
}
.active,
.accordion:hover {
background-color: #00000042;
border: 1px solid #fc8e2d;
}
.panel {
padding: 0 18px;
font-size: 18px;
background-color: #00000042;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
<div class="accordion-div">
<button class="accordion"><span class="faq__question-heading">Title1</span></button>
<div class="panel">
<p style="padding:18px 0;">description1</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title2</span></button>
<div class="panel">
<p style="padding:18px 0;">description2</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title3</span></button>
<div class="panel">
<p style="padding:18px 0;">description3</p>
</div>
</div>
Can anyone help me how I should change my code?
I tried changing the JavaScript by using this
for (var j = 0; j < acc.length; j++) {
acc[j].nextElementSibling.style.maxHeight = null;
}
but it messes up the entire JS and it stops working.
Change your code to this:
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
var panel = this.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
let active = document.querySelectorAll(".accordion-div .accordion.active");
for(let j = 0; j < active.length; j++){
active[j].classList.remove("active");
active[j].nextElementSibling.style.maxHeight = null;
}
// this.classList.toggle("active"); // move this down, outside of
// both if and else, since it should apply in any case
panel.style.maxHeight = panel.scrollHeight + "px";
}
this.classList.toggle("active"); // just add this
});
}
.accordion {
color: #fff;
background-color: #00000042;
cursor: pointer;
padding: 10px 10px;
margin-top:20px;
width: 100%;
border: 1px solid #00000042;
text-align: left;
outline: none;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #00000042;
border: 1px solid #fc8e2d;
}
.panel {
padding: 0 18px;
font-size: 18px;
background-color: #00000042;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
<div class="accordion-div">
<button class="accordion"><span class="faq__question-heading">Title1</span></button>
<div class="panel">
<p style="padding:18px 0;">description1</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title2</span></button>
<div class="panel">
<p style="padding:18px 0;">description2</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title3</span></button>
<div class="panel">
<p style="padding:18px 0;">description3</p>
</div>
</div>
The reason for the change - toggling of the active class should happen regardless of whether the clicked element had that class or not.
Also, as an aside - instead of having a click event handler for every panel (addEventListener within a loop), consider using delegation - attach the event handler on the parent element, and check if the receiver of the click was your panel.
For example:
var acc = document.querySelector(".accordion-div");
acc.addEventListener("click", function(e) {
var target = e.target;
if(!target.classList.contains("accordion")) {
return false;
}
var panel = target.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
let active = document.querySelectorAll(".accordion-div .accordion.active");
for(let j = 0; j < active.length; j++){
active[j].classList.remove("active");
active[j].nextElementSibling.style.maxHeight = null;
}
panel.style.maxHeight = panel.scrollHeight + "px";
}
target.classList.toggle("active");
});
.accordion {
color: #fff;
background-color: #00000042;
cursor: pointer;
padding: 10px 10px;
margin-top:20px;
width: 100%;
border: 1px solid #00000042;
text-align: left;
outline: none;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #00000042;
border: 1px solid #fc8e2d;
}
.panel {
padding: 0 18px;
font-size: 18px;
background-color: #00000042;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
<div class="accordion-div">
<button class="accordion"><span class="faq__question-heading">Title1</span></button>
<div class="panel">
<p style="padding:18px 0;">description1</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title2</span></button>
<div class="panel">
<p style="padding:18px 0;">description2</p>
</div>
<button class="accordion"><span class="faq__question-heading">Title3</span></button>
<div class="panel">
<p style="padding:18px 0;">description3</p>
</div>
</div>
Following this w3 guide to creating collapsible divs, but inside a React app. The cards I made only collapse after double clicking instead of single clicking. The only difference I can find is that I'm not using <script> and instead using onClick={function}.
Here's my code:
At top of Card component file:
function collapseElement() {
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
console.log(i);
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
}
In Card Component:
<div>
<button type="button" className="collapsible" onClick={collapseElement}>
<div className="collapsible-title">
{props.title}
</div>
</button>
<div className="content">
{props.children}
</div>
</div>
and the CSS:
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.active, .collapsible:hover {
background-color: #555;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
Thanks in advance!
You've set up the button's onClick to run collapseElement, which then goes through a list of elements with the "collapsible" class, and adds another click-event-listener to them to do the collapsing.
Either just run that function that adds a new click listener to every item, or just split out the actual function that toggles the content, and give that as the onClick tag.
i have site running on wordpress template.
i've pasted in the whole accordion code from W3School (here it is https://www.w3schools.com/howto/howto_js_accordion.asp).
my version unexpectedly doesn't work.
i've included css lines in the main.css file:
....
/* styles for 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: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
/*end styles for accordion*/
i've added the html code on the page through the admin console:
<h2>Accordion</h2>
<button class="accordion">Section 1</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
<button class="accordion">Section 2</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
<button class="accordion">Section 3</button>
<div class="panel"> <p>Lorem ipsum</p> </div>
next i've created scripts.js file and added it into the template with wp_enqueue_script in functions.php. so i can see it loads when the page code is shown.
content of the scripts.js file:
$(document).ready(function() {
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";
}
});
}
});
then nothing happens on click on the accordion: no js errors in the console, no content of the accordion shows up. how could i make it work?
upd 1
changed class names to unique ones
js
var acc = document.getElementsByClassName("nikaccordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("nikactive");
var nikpanel = this.nextElementSibling;
if (nikpanel.style.display === "block") {
nikpanel.style.display = "none";
} else {
nikpanel.style.display = "block";
}
});
}
css:
/* styles for accordion*/
.nikaccordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.nikactive, .nikaccordion:hover {
background-color: #ccc;
}
.nikpanel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
/*end styles for accordion*/
html:
<h2>Accordion</h2>
<button class="nikaccordion">Section 1</button>
<div class="nikpanel">
<p>Lorem ipsum</p>
</div>
<button class="nikaccordion">Section 2</button>
<div class="nikpanel">
<p>Lorem ipsum </p>
</div>
<button class="nikaccordion">Section 3</button>
<div class="nikpanel">
<p>Lorem ipsum </p>
</div>
upd 2
when im changing the order of the .js files that my file goes at the end of a list then i get an error
Uncaught TypeError: Cannot read property 'style' of null
at HTMLButtonElement.<anonymous> (scripts.js?ver=5.2.2:8)
so i suppose the
var nikpanel = this.nextElementSibling;
doesn't work
Your mistake is that you've mixed the basic version (non-animated) with the animated one.
For the basic version remove the min-height:0; and replace it with display:none.
This is because your max-height is 0 and your javascript code is changing the display value from 'none' to 'block' and vice-versa on click. Therefore, the element becomes a block element, however, it's height is limited by max-height:0.
Your CSS should be:
.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: #ccc;
}
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
Or you could just change your JS to change the max-height value instead of the display value.
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";
}
});
}
In your CSS replace
.panel {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
with
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
}
I've just tested your code and just that replacement get it working
i found out that WordPress adds p-tags to my html code thus the js script treated the phantom p-tags as target tags and tried to apply actions to them.
i disabled this addings by:
remove_filter( 'the_content', 'wpautop' );
remove_filter( 'the_excerpt', 'wpautop' );
in my functions.php
TLDR; The functionality I'm trying to create in this code example is upon clicking a box (collapsible has active class), and then you click on the search box, the active class is removed closing the collapsible. Currently, the remove class is not working. Any idea why?
In more detail:
I have a list of collapsible blocks get an active class when they are clicked. You can search a search box to filter out which block you want to be displayed, and thus click on it to display more content. I have realized that when you go back to search and didn't 'unclick' a box (to close the collapsible) the box is still active (which makes perfect sense).
I had an idea that upon search box focus, I would loop through all the collapsible and remove the active class itself (in turn, closing the collapsible).
I've gotten to the point where I can find which collapsible is active, but I cannot remove the active class.
Is this because the box was initially clicked to add the class and has to be physically clicked once again to remove it?
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
function searchClick() {
var searchTable;
searchTable = document.getElementById("search-table");
faqButtons = searchTable.getElementsByTagName("button");
for (i = 0; i < faqButtons.length; i++) {
if (faqButtons[i].classList.contains("active")) {
console.log('THIS ONE: ', faqButtons[i]);
faqButtons[i].classList.remove("active");
}
}
}
.collapsible {
display: flex;
justify-content: space-between;
background-color: white;
color: black;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 20px;
}
.collapsible span {
padding-left: 5px;
padding-right: 5px;
}
.collapsible .active,
.collapsible:hover {
background-color: #333;
color: white;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.content p {
margin-top: 10px;
}
<div class="search-faq">
<input autocomplete="off" type="text" id="search-faq" placeholder="Search for FAQ" onfocus="searchClick()" />
</div>
<div id="search-table" class="superHidden">
<div id="wolfandgrizzly-FAQ-search">
<h1>A to B sales</h1>
<button class="collapsible"><span>What are our shipping policies?</span></button>
<div class="content">
<p>
They are crazy cool.
</p>
</div>
<button class="collapsible"><span>Are you making more products?</span></button>
<div class="content">
<p>
We'll sell you more very soon
</p>
</div>
</div>
I don't see where you are getting that the active class is not being removed. It doesn't appear that there is a problem with that. I think the problem is that you are showing the answers via maxHeight setting, but you aren't resetting it when you click the search box. I've updated it in this snippet, but essentially it boiled down to adding the following in your loop:
var content = faqButtons[i].nextElementSibling
content.style.maxHeight = null
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
function searchClick() {
var searchTable;
searchTable = document.getElementById("search-table");
faqButtons = searchTable.getElementsByTagName("button");
for (i = 0; i < faqButtons.length; i++) {
if (faqButtons[i].classList.contains("active")) {
console.log('THIS ONE: ', faqButtons[i]);
faqButtons[i].classList.remove("active");
}
var content = faqButtons[i].nextElementSibling
content.style.maxHeight = null
}
}
.collapsible {
display: flex;
justify-content: space-between;
background-color: white;
color: black;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 20px;
}
.collapsible span {
padding-left: 5px;
padding-right: 5px;
}
.collapsible .active,
.collapsible:hover {
background-color: #333;
color: white;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.content p {
margin-top: 10px;
}
<div class="search-faq">
<input autocomplete="off" type="text" id="search-faq" placeholder="Search for FAQ" onfocus="searchClick()" />
</div>
<div id="search-table" class="superHidden">
<div id="wolfandgrizzly-FAQ-search">
<h1>A to B sales</h1>
<button class="collapsible"><span>What are our shipping policies?</span></button>
<div class="content">
<p>
They are crazy cool.
</p>
</div>
<button class="collapsible"><span>Are you making more products?</span></button>
<div class="content">
<p>
We'll sell you more very soon
</p>
</div>
</div>
Here's an approach that uses as little JavaScript as possible. If JavaScript is disabled, the only thing that will break is the search box. Sections will still open and close as expected when clicked.
The accordion stores its state in checkboxes, one in each section. Each section's title is a label element which toggles that section's checkbox when clicked. The sections are expanded and collapsed using CSS :checked selectors.
var sections = [].slice.call(document.querySelectorAll(".accordion li")),
searchAccordion = function() {
var value = document.getElementById("search").value.toLowerCase();
sections.map(function(section) {
var content = section.textContent.toLowerCase();
section.querySelector("input").checked = content.includes(value);
});
};
body {
font-family: sans-serif;
}
.accordion {
padding-left: 0;
margin: -1rem;
}
.accordion li {
list-style-type: none;
}
.accordion input {
display: none;
}
.accordion label {
background-color: #eee;
transition: background-color 100ms;
cursor: pointer;
font-size: 1.2rem;
padding: 1rem;
display: block;
}
.accordion label:hover {
background-color: #444;
color: white;
}
.accordion .content {
padding: 1rem;
display: none;
}
.accordion input:checked ~ .content {
display: block;
}
<input id="search" onKeyup="searchAccordion()" type="text" placeholder="Search for FAQ" autocomplete="off">
<h1>A to B sales</h1>
<ul class="accordion">
<li>
<input type="checkbox" id="section1">
<label for="section1">What are our shipping policies?</label>
<div class="content">They are crazy cool.</div>
</li>
<li>
<input type="checkbox" id="section2">
<label for="section2">Are you making more products?</label>
<div class="content">We'll sell you more very soon</div>
</li>
</ul>
The accordion was working fine until I moved the <h2> tag outside of the button as it was failing html5 validation.
I do not know too much of javascript and found the code on the w3schools website. it works just fine when the <h2> tag is inside the <button> tag.
HTML:
<h2><button class="accordion" id="uses">Lorem ipsum</button></h2>
<div class="panel">
<div class="accordioncontent">
<h3>Lorem ipsum...</h3>
</div>
</div>
JS:
<script type="text/javascript">
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";
}
}
}
</script>
CSS:
h2 button.accordion {
display: inline-block;
float: left;
width: 100%;
font-size: 43px;
font-family: 'Open Sans', sans-serif;
text-align: left;
}
button.accordion:after {
content: '\02795';
font-size: 25px;
float: right;
margin-top: 16px;
}
button.accordion.active:after {
content: "\2716";
}
div.panel {
width: 880px;
max-height: 0;
margin: 0 auto;
background-color: white;
overflow: hidden;
}
.accordioncontent {
padding: 18px 25px;
min-height: 250px;
}
The error is due this line:
this.nextElementSibling;
While the h2 is inside the button, the sibling of the element button is the div with class .panel. Once you put the button inside a h2 the nextElementSibling it doesn't find a sibling because it's the only child inside the element.
You should imagine all this like a tree
Document
+-H2
| +-Button
+-div.panel
Check the info about the DOM tree https://www.w3schools.com/js/js_htmldom.asp and the info about how is working the nextElementSibling https://www.w3schools.com/jsref/prop_element_nextelementsibling.asp property of an element
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
this.classList.toggle("active");
console.dir(this);
<!--No siblings for button inside h2 element-->
console.dir(this.nextElementSibling);
<!--Existing sibling for parent Node-->
var panel = this.parentNode.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
}
}
h2 button.accordion {
display: inline-block;
float: left;
width: 100%;
font-size: 43px;
font-family: 'Open Sans', sans-serif;
text-align: left;
}
button.accordion:after {
content: '\02795';
font-size: 25px;
float: right;
margin-top: 16px;
}
button.accordion.active:after {
content: "\2716";
}
div.panel {
width: 880px;
max-height: 0;
margin: 0 auto;
background-color: white;
overflow: hidden;
}
.accordioncontent {
padding: 18px 25px;
min-height: 250px;
}
<h2><button class="accordion" id="uses">Lorem ipsum</button></h2>
<div class="panel">
<div class="accordioncontent">
<h3>Lorem ipsum...</h3>
</div>
</div>
Check your CSS, you are using h2 tag.
h2 button.accordion {
display: inline-block;
float: left;
width: 100%;
font-size: 43px;
font-family: 'Open Sans', sans-serif;
text-align: left;
}
You have to change this js line:
var panel = this.nextElementSibling;
for this:
var panel = this.parentNode.nextElementSibling;
Becouse the nextElementSibling function try get the next html object after button. If you have h2 tag inside button tag then the next tag is div tag with panel class but if you moved Button inside h2 tag then Button is the last object and the nextElementSibling function returns nothing.
Call the parentNode function before nextElementSibling function will return the next object after parent of button. In this case parent of button tag is h2 tag and next object after h2 tag is the div tag with panel class.