Search code examples
jquerytwitter-bootstrap-3nestedaccordion

jQuery Bootstrap Nested Accordions: How to collapse child items when parent is closed


I have nested Bootstrap Accordions where I want to scroll to the top of the item expanded. The problem is, expanded sub-items higher up in the DOM cause problems locating the item that's actually being expanded.

I've searched through all the posts that mention this, but my code looks very different to those and I can't get it working.

Can anyone help me automatically close all sub-items if a parent gets collapsed due to another parent being expanded?

UPDATED CODE WITH WORKING VERSION:

$(function () {
  $('.panel-group').on('shown.bs.collapse', function (e) {
    var offset = $(this).find('.collapse.in').prev('.panel-heading');
    if(offset) {
      $('html,body').animate({
        scrollTop: $(offset).offset().top -6
      }, 500);
      e.stopPropagation();
    }
  });

  $('.panel-collapse').on('hidden.bs.collapse', function () {
    // find the children and close them
    $(this).find('.collapse.in').collapse('hide');
  });

});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>

<p>&nbsp;</p>
<p>&nbsp;</p>

<div class="panel-group level1" id="accordion" role="tablist" aria-multiselectable="true">

  <div class="panel panel-default">
    <div class="panel-heading" role="tab" id="heading1">
      <h4 class="panel-title">
        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapse1" aria-expanded="true" aria-controls="collapse1">
          Item 1
        </a>
      </h4>
    </div>
    <div id="collapse1" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading1">
      <div class="panel-body">

			<div class="panel-group level2" id="accordion1" role="tablist" aria-multiselectable="true">
			
			  <div class="panel panel-default">
			    <div class="panel-heading" role="tab" id="heading1_1">
			      <h4 class="panel-title">
			        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion1" href="#collapse1_1" aria-expanded="true" aria-controls="collapse1_1">
			          Item 1.1
			        </a>
			      </h4>
			    </div>
			    <div id="collapse1_1" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading1_1">
			      <div class="panel-body">
			      	Lorem ipsum ...
			      </div>
			    </div>
			  </div>
			  
			  <div class="panel panel-default">
			    <div class="panel-heading" role="tab" id="heading1_2">
			      <h4 class="panel-title">
			        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion1" href="#collapse1_2" aria-expanded="true" aria-controls="collapse1_2">
			          Item 1.2
			        </a>
			      </h4>
			    </div>
			    <div id="collapse1_2" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading1_2">
			      <div class="panel-body">
			      	Lorem ipsum ...
			      </div>
			    </div>
			  </div>
			
			</div>

      </div>
    </div>
  </div>
  
  <div class="panel panel-default">
    <div class="panel-heading" role="tab" id="heading2">
      <h4 class="panel-title">
        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapse2" aria-expanded="true" aria-controls="collapse2">
          Item 2
        </a>
      </h4>
    </div>
    <div id="collapse2" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading2">
      <div class="panel-body">
      
			<div class="panel-group level2" id="accordion2" role="tablist" aria-multiselectable="true">
			
			  <div class="panel panel-default">
			    <div class="panel-heading" role="tab" id="heading2_1">
			      <h4 class="panel-title">
			        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion2" href="#collapse2_1" aria-expanded="true" aria-controls="collapse2_1">
			          Item 1.1
			        </a>
			      </h4>
			    </div>
			    <div id="collapse2_1" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading2_1">
			      <div class="panel-body">
			      	Lorem ipsum ...
			      </div>
			    </div>
			  </div>
			  
			  <div class="panel panel-default">
			    <div class="panel-heading" role="tab" id="heading2_2">
			      <h4 class="panel-title">
			        <a role="button" class="collapsed" data-toggle="collapse" data-parent="#accordion2" href="#collapse2_2" aria-expanded="true" aria-controls="collapse2_2">
			          Item 1.2
			        </a>
			      </h4>
			    </div>
			    <div id="collapse2_2" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading2_2">
			      <div class="panel-body">
			      	Lorem ipsum ...
			      </div>
			    </div>
			  </div>
			
			</div>


      </div>
    </div>
  </div>

</div>

<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>

See attached code to replicate problem:

  1. Expand Item 1, then Item 2, then Item 1 again - note scrolling works
  2. Expand Item 1.2 - again, scrolling works
  3. Now expand Item 2 - because Item 1.2 is expanded within the now collapsed Item 1, it's Item 1 that is being scrolled to.

Thanks in advance, Justin


Solution

  • Bootstrap 5 (Update 2023)

    jQuery is no longer native to Bootstrap 5 so vanilla JS will achieve the same functionality to close the children...

    const myCollapsible = document.getElementById('module-test')
    myCollapsible.addEventListener('hidden.bs.collapse', event => {
       // close children
       event.target.querySelectorAll('.collapse').forEach(element => {
           const bc = bootstrap.Collapse.getOrCreateInstance(element)
           bc.hide()
       })
    })
    

    https://codeply.com/p/mJYXqUDGm6


    Bootstrap 3 (Original Answer)

    It should be as simple as handling the hidden event..

    $('.panel-collapse').on('hidden.bs.collapse', function () {
      // find the children and close them
      $(this).find('.collapse').collapse('hide');
    });
    

    Bootstrap 3: https://codeply.com/p/MGamArJJ7I