22

I'm using Bootstrap 3 and trying to setup the following accordion/collapse structure:

  1. Onload: Each accordion panel in a group is fully collapsed and functions as documented/expected.

  2. Button click: Each accordion panel expands and clicking the toggles has no effect (including URL anchor effects).

  3. Another button click: All panels return to onload state; all collapsed and clickable as normal.

I've made it to step 2, but when I click the button again at step 3 it has no effect. I also see no console errors reported in Chrome Dev Tools or by running the code through my local JSHint.

I'd like this cycle to be repeatable each time the button is clicked.

I've setup my code here http://bootply.com/98140 and here http://jsfiddle.net/A9vCx/

I'd love to know what I'm doing wrong and I appreciate suggestions. Thank you!

My HTML:

<button class="collapse-init">Click to disable accordion behavior</button>
<br><br>
<div class="panel-group" id="accordion">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h4 class="panel-title">
        <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
          Collapsible Group Item #1
        </a>
      </h4>
    </div>
    <div id="collapseOne" class="panel-collapse collapse">
      <div class="panel-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
      </div>
    </div>
  </div>
  <div class="panel panel-default">
    <div class="panel-heading">
      <h4 class="panel-title">
        <a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">
          Collapsible Group Item #2
        </a>
      </h4>
    </div>
    <div id="collapseTwo" class="panel-collapse collapse">
      <div class="panel-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
      </div>
    </div>
  </div>
  <div class="panel panel-default">
    <div class="panel-heading">
      <h4 class="panel-title">
        <a data-toggle="collapse" data-parent="#accordion" href="#collapseThree">
          Collapsible Group Item #3
        </a>
      </h4>
    </div>
    <div id="collapseThree" class="panel-collapse collapse">
      <div class="panel-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
      </div>
    </div>
  </div>
</div>

My JS:

$(function() {

  var $active = true;

  $('.panel-title > a').click(function(e) {
    e.preventDefault();
  });

  $('.collapse-init').on('click', function() {
    if(!$active) {
      $active = true;
      $('.panel-title > a').attr('data-toggle', 'collapse');
      $('.panel-collapse').collapse({'toggle': true, 'parent': '#accordion'});
      $(this).html('Click to disable accordion behavior');
    } else {
      $active = false;
      $('.panel-collapse').collapse({'toggle': true, 'parent': '#accordion'});
      $('.panel-title > a').removeAttr('data-toggle');
      $(this).html('Click to enable accordion behavior');
    }
  });

});
strivedi183
  • 4,408
  • 2
  • 25
  • 34
cfx
  • 2,885
  • 2
  • 30
  • 43

4 Answers4

50

Updated Answer

Trying to open multiple panels of a collapse control that is setup as an accordion i.e. with the data-parent attribute set, can prove quite problematic and buggy (see this question on multiple panels open after programmatically opening a panel)

Instead, the best approach would be to:

  1. Allow each panel to toggle individually
  2. Then, enforce the accordion behavior manually where appropriate.

To allow each panel to toggle individually, on the data-toggle="collapse" element, set the data-target attribute to the .collapse panel ID selector (instead of setting the data-parent attribute to the parent control. You can read more about this in the question Modify Twitter Bootstrap collapse plugin to keep accordions open.

Roughly, each panel should look like this:

<div class="panel panel-default">
   <div class="panel-heading">
         <h4 class="panel-title"
             data-toggle="collapse" 
             data-target="#collapseOne">
             Collapsible Group Item #1
         </h4>
    </div>
    <div id="collapseOne" 
         class="panel-collapse collapse">
        <div class="panel-body"></div>
    </div>
</div>

To manually enforce the accordion behavior, you can create a handler for the collapse show event which occurs just before any panels are displayed. Use this to ensure any other open panels are closed before the selected one is shown (see this answer to multiple panels open). You'll also only want the code to execute when the panels are active. To do all that, add the following code:

$('#accordion').on('show.bs.collapse', function () {
    if (active) $('#accordion .in').collapse('hide');
});

Then use show and hide to toggle the visibility of each of the panels and data-toggle to enable and disable the controls.

$('#collapse-init').click(function () {
    if (active) {
        active = false;
        $('.panel-collapse').collapse('show');
        $('.panel-title').attr('data-toggle', '');
        $(this).text('Enable accordion behavior');
    } else {
        active = true;
        $('.panel-collapse').collapse('hide');
        $('.panel-title').attr('data-toggle', 'collapse');
        $(this).text('Disable accordion behavior');
    }
});

Working demo in jsFiddle

Community
  • 1
  • 1
KyleMit
  • 45,382
  • 53
  • 367
  • 544
  • Soooo close! If I click panel 1, then click the `button` twice, then click panel 1, then click panel 2, then click the `button` again, panel 2 hides and remains closed :-( – cfx Dec 03 '13 at 16:18
  • It's also possible to break this by loading fresh, then click on panel 1, then click panel 2, then click panel 3, then click the `button` and panel 3 hides when it should show. – cfx Dec 03 '13 at 16:42
  • Very close and I just want to thank you again–I wish I could accept both answers since they're both indispensable! – cfx Dec 03 '13 at 18:56
  • @gordian, I've updated the solution to what I think is the cleanest one yet out of all the suggestions. Still, it was a nice group effort. – KyleMit Dec 03 '13 at 19:07
  • Definitely the cleanest of the two. Thank you and @Trevor again! – cfx Dec 03 '13 at 19:52
  • I cleaned it up a bit more even, since we don't need the `href` I replaced the panel toggle anchor with a `

    ` which also means we can remove the `preventDefault()` section! http://jsfiddle.net/3NuHh/2/

    – cfx Dec 03 '13 at 20:01
  • 1
    I cleaned it up even more! As we probably don't need the `

    ` element if it's the only thing in the `panel-title` [**jsFiddle**](http://jsfiddle.net/KyleMit/f8ypa/4/) :)

    – KyleMit Dec 03 '13 at 20:19
  • Problem with this solution: nested accordions completely break it because the `show.bs.collapse` event bubbles up. [See broken jsfiddle here](https://jsfiddle.net/FireSBurnsmuP/v9dmfvxh/1/) If you then register the `show.bs.collapse` event with every collapsible panel within the accordion's panels that prevents bubbling, this might work still, but I have a feeling that will also break things. More on that in a moment. – FireSBurnsmuP Oct 16 '15 at 16:08
  • @FireSBurnsmuP, I wouldn't say so much that that's a deficiency of this particular solution, but a brand new use case to consider. No one snippet is going to work if you just copy pasta anywhere else, but there's probably a way to leverage this process for your particular markup. It's supposed to represent the simplest possible code so as many people can adapt it to their particular situation. In 99% of cases, it'll be fine so there's no need to add extra code for a hypothetical more complex example. – KyleMit Oct 16 '15 at 17:32
  • @KyleMit Understood. However, it is worth noting that more work will be needed for those of us who have more complex situations. – FireSBurnsmuP Oct 16 '15 at 18:55
  • @FireSBurnsmuP, respectfully, I disagree. I don't think it's worthwhile pointing out `"that more work will be needed for those of us who have more complex situations"`. That's a truism for just about every question on stackoverflow. There's an infinite number of more complex situations, and they should not be addressed in the question or identified in the comments, otherwise it would get very noisy. An answer should get you on the right path for other implementations. If you're having trouble implementing it, it's a great reason to open up a new question that explains what's different/broken – KyleMit Oct 16 '15 at 19:27
4

For whatever reason $('.panel-collapse').collapse({'toggle': true, 'parent': '#accordion'}); only seems to work the first time and it only works to expand the collapsible. (I tried to start with a expanded collapsible and it wouldn't collapse.)

It could just be something that runs once the first time you initialize collapse with those parameters.

You will have more luck using the show and hide methods.

Here is an example:

$(function() {

  var $active = true;

  $('.panel-title > a').click(function(e) {
    e.preventDefault();
  });

  $('.collapse-init').on('click', function() {
    if(!$active) {
      $active = true;
      $('.panel-title > a').attr('data-toggle', 'collapse');
      $('.panel-collapse').collapse('hide');
      $(this).html('Click to disable accordion behavior');
    } else {
      $active = false;
      $('.panel-collapse').collapse('show');
      $('.panel-title > a').attr('data-toggle','');
      $(this).html('Click to enable accordion behavior');
    }
  });

});

http://bootply.com/98201

Update

Granted KyleMit seems to have a way better handle on this then me. I'm impressed with his answer and understanding.

I don't understand what's going on or why the show seemed to be toggling in some places.

But After messing around for a while.. Finally came with the following solution:

$(function() {
  var transition = false;
  var $active = true;

  $('.panel-title > a').click(function(e) {
    e.preventDefault();
  });

  $('#accordion').on('show.bs.collapse',function(){
    if($active){
        $('#accordion .in').collapse('hide');
    }
  });

  $('#accordion').on('hidden.bs.collapse',function(){
    if(transition){
        transition = false;
        $('.panel-collapse').collapse('show');
    }
  });

  $('.collapse-init').on('click', function() {
    $('.collapse-init').prop('disabled','true');
    if(!$active) {
      $active = true;
      $('.panel-title > a').attr('data-toggle', 'collapse');
      $('.panel-collapse').collapse('hide');
      $(this).html('Click to disable accordion behavior');
    } else {
      $active = false;
      if($('.panel-collapse.in').length){
        transition = true;
        $('.panel-collapse.in').collapse('hide');       
      }
      else{
        $('.panel-collapse').collapse('show');
      }
      $('.panel-title > a').attr('data-toggle','');
      $(this).html('Click to enable accordion behavior');
    }
    setTimeout(function(){
        $('.collapse-init').prop('disabled','');
    },800);
  });
});

http://bootply.com/98239

Trevor
  • 15,719
  • 7
  • 49
  • 78
  • 3
    Thanks @Trevor. The problem with using the `show` method is that if you click the `button` twice then open panel 1, when you click to open panel 2 then panel 1 remains open, almost like it doesn't remember its grouping. – cfx Dec 03 '13 at 15:31
  • 2
    +1 great answer. To expand on needing to use `show`: When you call the `collapse` function *(or any bootstrap function)*, if you pass an object it means that you **initiate** the collapse. The [`toggle` option](http://getbootstrap.com/javascript/#collapse) only `toggles the collapsible element on invocation` (from [this answer](http://stackoverflow.com/a/11207682/1366033)) – KyleMit Dec 03 '13 at 15:33
  • Thank you @KyleMit! I'm much closer now. I added a check but now the issue I find is that if you load the fiddle fresh, then show panel 1 and click the `button` then panel 1 closes (toggles) instead of showing like the rest. http://bootply.com/98224 – cfx Dec 03 '13 at 16:05
  • @gordian, see my answer which should perform everything as needed. – KyleMit Dec 03 '13 at 16:11
  • for some reason `e.stopPropagation()` does not work for me, so I simply used `return false;` instead – Ivan Feb 10 '16 at 16:15
3

To keep the accordion nature intact when wanting to also use 'hide' and 'show' functions like .collapse( 'hide' ), you must initialize the collapsible panels with the parent property set in the object with toggle: false before making any calls to 'hide' or 'show'

// initialize collapsible panels
$('#accordion .collapse').collapse({
  toggle: false,
  parent: '#accordion'
});

// show panel one (will collapse others in accordion)
$( '#collapseOne' ).collapse( 'show' );

// show panel two (will collapse others in accordion)
$( '#collapseTwo' ).collapse( 'show' );

// hide panel two (will not collapse/expand others in accordion)
$( '#collapseTwo' ).collapse( 'hide' );
mike
  • 51
  • 2
-1

The best and tested solution is to put the following small snippet which will collapse the accordion tab which is already open when you load. In my case the last sixth tab was open so I made it collapsed on page load.

 $(document).ready(){
      $('#collapseSix').collapse("hide");
 }
Sheryar Nizar
  • 255
  • 4
  • 4