35

I'm using the jQuery UI Accordion (which does not allow more than one item open at a time) on a project. Using accordion is appropriate since I usually do only want one panel open at a time.

However, I need to offer an "Expand All" link which switches to "Collapse All" when clicked. I don't want to custom write near-identical accordion functionality around this one requirement, so I'd like some JS that will achieve this whilst keeping the Accordion component in use.

Question: What JavaScript/jQuery is required to achieve this whilst still using the jQuery UI "Accordion" component to power the standard functionality?

Here's a fiddle: http://jsfiddle.net/alecrust/a6Cu7/

AlecRust
  • 7,882
  • 10
  • 39
  • 55

14 Answers14

53

As discussed in the jQuery UI forums, you should not use accordions for this.

If you want something that looks and acts like an accordion, that is fine. Use their classes to style them, and implement whatever functionality you need. Then adding a button to open or close them all is pretty straightforward. Example

HTML

By using the jquery-ui classes, we keep our accordions looking just like the "real" accordions.

<div id="accordion" class="ui-accordion ui-widget ui-helper-reset">
    <h3 class="accordion-header ui-accordion-header ui-helper-reset ui-state-default ui-accordion-icons ui-corner-all">
        <span class="ui-accordion-header-icon ui-icon ui-icon-triangle-1-e"></span>
        Section 1
    </h3>
    <div class="ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom">
        Content 1
    </div>
</div>​

Roll your own accordions

Mostly we just want accordion headers to toggle the state of the following sibling, which is it's content area. We have also added two custom events "show" and "hide" which we will hook into later.

var headers = $('#accordion .accordion-header');
var contentAreas = $('#accordion .ui-accordion-content ').hide();
var expandLink = $('.accordion-expand-all');

headers.click(function() {
    var panel = $(this).next();
    var isOpen = panel.is(':visible');

    // open or close as necessary
    panel[isOpen? 'slideUp': 'slideDown']()
        // trigger the correct custom event
        .trigger(isOpen? 'hide': 'show');

    // stop the link from causing a pagescroll
    return false;
});

Expand/Collapse All

We use a boolean isAllOpen flag to mark when the button has been changed, this could just as easily have been a class, or a state variable on a larger plugin framework.

expandLink.click(function(){
    var isAllOpen = $(this).data('isAllOpen');

    contentAreas[isAllOpen? 'hide': 'show']()
        .trigger(isAllOpen? 'hide': 'show');
});

Swap the button when "all open"

Thanks to our custom "show" and "hide" events, we have something to listen for when panels are changing. The only special case is "are they all open", if yes the button should be a "Collapse all", if not it should be "Expand all".

contentAreas.on({
    // whenever we open a panel, check to see if they're all open
    // if all open, swap the button to collapser
    show: function(){
        var isAllOpen = !contentAreas.is(':hidden');   
        if(isAllOpen){
            expandLink.text('Collapse All')
                .data('isAllOpen', true);
        }
    },
    // whenever we close a panel, check to see if they're all open
    // if not all open, swap the button to expander
    hide: function(){
        var isAllOpen = !contentAreas.is(':hidden');
        if(!isAllOpen){
            expandLink.text('Expand all')
            .data('isAllOpen', false);
        } 
    }
});​

Edit for comment: Maintaining "1 panel open only" unless you hit the "Expand all" button is actually much easier. Example

Sinetheta
  • 8,735
  • 4
  • 27
  • 52
  • Thanks for this. As explained, I do understand that I *should not* be using accordion for this, but my question is whether it's **possible** over the top of the existing jQuery UI Accordion component. It would be one thing if your relatively large amount of jQuery produced an identical accordion to jQuery UI's (only one panel open at a time etc.) but it does not. – AlecRust Oct 16 '12 at 09:41
  • 2
    Is it possible? The simple answer is no. You could edit the source in a lot of separate places to make it so, but that isn't using it, that's rewriting it. And yes, "large" is relative. The jQuery ui accordion source is 738 lines of code, and that's only because it's built on jquery.ui.core.js and jquery.ui.widget.js – Sinetheta Oct 16 '12 at 16:56
  • Many thanks. Was interesting to see if this was possible, thanks for your replies. – AlecRust Oct 16 '12 at 20:34
  • One last question: How would I reproduce the first item being open, like jQuery UI Accordion does by default? – AlecRust Oct 17 '12 at 11:37
  • by adding `.first().show()` to the line which initially hides all of the content panels. I updated the final example. – Sinetheta Oct 17 '12 at 16:13
  • That seems to make the panels un-collapsible. It's OK, have solved with [this hybrid solution](http://stackoverflow.com/a/12936249/312681). – AlecRust Oct 17 '12 at 20:06
  • Your example does not work fo ALL elements. Just check it. – DmitryBoyko Jun 22 '15 at 17:59
  • Thanks for pointing that out. Weird that it remained broken for so long. Because of the "show first accordion" `contentAreas` was only referencing the first content area, when it needed to reference *all* content areas. I fixed that by adding a `.end()` to reset the selector. – Sinetheta Jun 23 '15 at 06:57
  • This is a great answer. I modified your code to where all the panels open on page load. Wish I could buy you a beer! – JoshYates1980 Jun 24 '15 at 16:55
  • I know this is a really old answer, but I'm stuck with this thing now and need help. I really like your fiddle, but is there a way to make sure when you click on one header, the other one closes? – DeA May 01 '17 at 18:01
  • I'm not sure I understand @DeA is that not the current behaviour? Click on any header, the other sections close. Expand All, then click on any header, they all close but the one you clicked re-opens. – Sinetheta May 02 '17 at 03:22
  • That's true. By default that is the behavior, but in that fiddle, it doesn't behave that way – DeA May 02 '17 at 18:57
  • @Sinetheta Thank you so much! Wow. That really helped. I was not expecting a response for an old post. But, thank you! – DeA May 03 '17 at 14:38
19

A lot of these seem to be overcomplicated. I achieved what I wanted with just the following:

$(".ui-accordion-content").show();

JSFiddle

Charles Clayton
  • 13,212
  • 10
  • 73
  • 114
18

This my solution:

Working in real project.

   $(function () {
    $("#accordion").accordion({collapsible:true, active:false});
    $('.open').click(function () {
        $('.ui-accordion-header').removeClass('ui-corner-all').addClass('ui-accordion-header-active ui-state-active ui-corner-top').attr({'aria-selected':'true','tabindex':'0'});
        $('.ui-accordion-header .ui-icon').removeClass('ui-icon-triangle-1-e').addClass('ui-icon-triangle-1-s');
        $('.ui-accordion-content').addClass('ui-accordion-content-active').attr({'aria-expanded':'true','aria-hidden':'false'}).show();
        $(this).hide();
        $('.close').show();
    });
    $('.close').click(function () {
        $('.ui-accordion-header').removeClass('ui-accordion-header-active ui-state-active ui-corner-top').addClass('ui-corner-all').attr({'aria-selected':'false','tabindex':'-1'});
        $('.ui-accordion-header .ui-icon').removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-1-e');
        $('.ui-accordion-content').removeClass('ui-accordion-content-active').attr({'aria-expanded':'false','aria-hidden':'true'}).hide();
        $(this).hide();
        $('.open').show();
    });
    $('.ui-accordion-header').click(function () {
        $('.open').show();
        $('.close').show();
    });
});

http://jsfiddle.net/bigvax/hEApL/

Taifun
  • 6,084
  • 17
  • 53
  • 180
bigvax
  • 189
  • 1
  • 4
  • 4
    Hi bigvax, the jsfiddle link is great, but I have few issues with that: here is the scenario: 1) click "section 1" to open it. 2) click "Collapse All" button to close the previously opened section. 3) Now click again "section 1", it does not opens in first click, you have to click it "twice". I tried to resolve it, but could not. Can you please tell me how can I solve this issue? – jeewan Jun 18 '13 at 17:15
  • I had the same problem as @jeewan – DontFretBrett Feb 09 '15 at 22:54
  • same here. for some reason it does not close on first click. – Leoncio Jun 10 '16 at 17:49
  • Same here, needs two clicks to close – Pedro Aug 04 '20 at 19:42
7

In the end I found this to be the best solution considering the requirements:

// Expand/Collapse all
$('.accordion-expand-collapse a').click(function() {
    $('.accordion .ui-accordion-header:not(.ui-state-active)').next().slideToggle();
    $(this).text($(this).text() == 'Expand all' ? 'Collapse all' : 'Expand all');
    $(this).toggleClass('collapse');
    return false;
});

Updated JSFiddle Link: http://jsfiddle.net/ccollins1544/r8j105de/4/

AlecRust
  • 7,882
  • 10
  • 39
  • 55
  • Copy this and run on your fiddle link, not working... Here is the code that works with your fiddle link ` $('.accordion-expand-all a').click(function() { $('#accordion .ui-accordion-header:not(.ui-state-active)').next().slideToggle(); $(this).text($(this).text() == 'Expand all' ? 'Collapse all' : 'Expand all'); $(this).toggleClass('collapse'); return false; });` – Phung D. An Nov 13 '14 at 09:45
  • What if there is a nested accordion? Will this code work? – DmitryBoyko Jun 23 '15 at 12:17
3

I don't believe you can do this with an accordion since it's specifically designed preserve the property that at most one item will be opened. However, even though you say you don't want to re-implement accordion, you might be over estimating the complexity involved.

Consider the following scenario where you have a vertical stack of elements,

++++++++++++++++++++
+     Header 1     +
++++++++++++++++++++
+                  +
+      Item 1      +
+                  +
++++++++++++++++++++
+     Header 2     +
++++++++++++++++++++
+                  +
+      Item 2      +
+                  +
++++++++++++++++++++

If you're lazy you could build this using a table, otherwise, suitably styled DIVs will also work.

Each of the item blocks can have a class of itemBlock. Clicking on a header will cause all elements of class itemBlock to be hidden ($(".itemBlock").hide()). You can then use the jquery slideDown() function to expand the item below the header. Now you've pretty much implemented accordion.

To expand all items, just use $(".itemBlock").show() or if you want it animated, $(".itemBlock").slideDown(500). To hide all items, just use $(".itemBlock").hide().

PhilDin
  • 2,622
  • 3
  • 20
  • 36
  • Thanks, I may well go with this if all else fails. Still holding out hope for a function that can be added alongside the standard Accordion :) – AlecRust Oct 11 '12 at 15:57
  • Unfortunately the "only 1 open" functionality is [fundamental](https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.accordion.js#L105-L116) to the jquery ui accordions, and couldn't be removed without overwriting `_create`, which really means forking the widget. I've added an answer with everything sorted out for you though. – Sinetheta Oct 15 '12 at 20:25
  • It seems strange to me that the whole widget would need to be forked. I'd like all existing functionality of jQuery UI Accordion (every part of it, including only one panel open at a time) but the ability to expand all panels with a button. Perhaps this is just a matter of disabling the accordion widget completely to render all items uncollapsed? – AlecRust Oct 16 '12 at 09:43
  • 1
    Sorry I misunderstood your question, since "Expand all" is mutually exclusive to "only 1 open". I have amended my answer with an example, since "only 1 open unless you hit expand all" is actually much easier to code for. – Sinetheta Oct 16 '12 at 17:08
2

Here's the code by Sinetheta converted to a jQuery plugin: Save below code to a js file.

$.fn.collapsible = function() {
    $(this).addClass("ui-accordion ui-widget ui-helper-reset");
    var headers = $(this).children("h3");
    headers.addClass("accordion-header ui-accordion-header ui-helper-reset ui-state-active ui-accordion-icons ui-corner-all");
    headers.append('<span class="ui-accordion-header-icon ui-icon ui-icon-triangle-1-s">');
    headers.click(function() {
        var header = $(this);
        var panel = $(this).next();
        var isOpen = panel.is(":visible");
        if(isOpen)  {
            panel.slideUp("fast", function() {
                panel.hide();
                header.removeClass("ui-state-active")
                    .addClass("ui-state-default")
                    .children("span").removeClass("ui-icon-triangle-1-s")
                        .addClass("ui-icon-triangle-1-e");
          });
        }
        else {
            panel.slideDown("fast", function() {
                panel.show();
                header.removeClass("ui-state-default")
                    .addClass("ui-state-active")
                    .children("span").removeClass("ui-icon-triangle-1-e")
                        .addClass("ui-icon-triangle-1-s");
          });
        }
    });
}; 

Refer it in your UI page and call similar to jQuery accordian call:

$("#accordion").collapsible(); 

Looks cleaner and avoids any classes to be added to the markup.

Community
  • 1
  • 1
2

I second bigvax comment earlier but you need to make sure that you add

        jQuery("#jQueryUIAccordion").({ active: false,
                              collapsible: true });

otherwise you wont be able to open the first accordion after collapsing them.

    $('.close').click(function () {
    $('.ui-accordion-header').removeClass('ui-accordion-header-active ui-state-active ui-corner-top').addClass('ui-corner-all').attr({'aria-selected':'false','tabindex':'-1'});
    $('.ui-accordion-header .ui-icon').removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-1-e');
    $('.ui-accordion-content').removeClass('ui-accordion-content-active').attr({'aria-expanded':'false','aria-hidden':'true'}).hide();
   }
craig_nelson
  • 167
  • 1
  • 8
1

try this one jquery-multi-open-accordion, might help you

rajesh kakawat
  • 10,330
  • 1
  • 18
  • 38
  • Thanks for this, I gave it a try but unfortunately it's very out of date with the latest jQuery/jQuery UI library and no longer has compatibility with the latest versions. – AlecRust Oct 16 '12 at 09:34
  • This works great except doesn't have standard accordion functionality (only one panel open at a time). I'd like it to match the jQuery UI Accordion but also offer an override button to expand all panels. – AlecRust Oct 16 '12 at 10:44
  • Again, almost! The accordion now seems to function as normal, but the "Expand all" link doesn't correctly expand all panels like the accordion would, and doesn't switch to "Collapse all" when clicked. – AlecRust Oct 16 '12 at 14:57
0
Yes, it is possible. Put all div in separate accordion class as follows:

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery-ui.js"></script>

<script type="text/javascript">

        $(function () {
            $("input[type=submit], button")
        .button()
        .click(function (event) {
            event.preventDefault();
        });
            $("#tabs").tabs();
            $(".accordion").accordion({
                heightStyle: "content",

                collapsible: true,
                active: 0



            });
        });

function expandAll()
{
  $(".accordion").accordion({
                heightStyle: "content",

                collapsible: true,
                active: 0

            });

            return false;   
}

function collapseAll()
{
  $(".accordion").accordion({
                heightStyle: "content",

                collapsible: true,
                active: false



            });
            return false;
}
</script>



<div class="accordion">
  <h3>Toggle 1</h3>
  <div >
    <p>text1.</p>
  </div>
</div>
<div class="accordion">
  <h3>Toggle 2</h3>
  <div >
    <p>text2.</p>
  </div>
</div>
<div class="accordion">
  <h3>Toggle 3</h3>
  <div >
    <p>text3.</p>
  </div>
</div>
0

You can try this lightweight small plugin.

It will allow you customize it as per your requirement. It will have Expand/Collapse functionality.

URL: http://accordion-cd.blogspot.com/

Dharam Mali
  • 826
  • 1
  • 10
  • 22
0

I found AlecRust's solution quite helpful, but I add something to resolve one problem: When you click on a single accordion to expand it and then you click on the button to expand, they will all be opened. But, if you click again on the button to collapse, the single accordion expand before won't be collapse.

I've used imageButton, but you can also apply that logic to buttons.

/*** Expand all ***/
$(".expandAll").click(function (event) {
    $('.accordion .ui-accordion-header:not(.ui-state-active)').next().slideDown();

    return false;
});

/*** Collapse all ***/
$(".collapseAll").click(function (event) {
    $('.accordion').accordion({
        collapsible: true,
        active: false
    });

    $('.accordion .ui-accordion-header').next().slideUp();

    return false;
});

Also, if you have accordions inside an accordion and you want to expand all only on that second level, you can add a query:

/*** Expand all Second Level ***/
$(".expandAll").click(function (event) {
    $('.accordion .ui-accordion-header:not(.ui-state-active)').nextAll(':has(.accordion .ui-accordion-header)').slideDown();

    return false;
});
Freelex
  • 176
  • 3
  • 10
0

Using an example about for Taifun, I modified to allow expand and collapse.

... // hook up the expand/collapse all

var expandLink = $('.accordion-expand-all');

expandLink.click(function () {

$(".ui-accordion-content").toggle();
});
NCR
  • 1
  • 1
0

I tried the old-fashioned version, of just adjusting aria-* and CSS attributes like many of these older answers, but eventually gave up and just did a conditional fake-click. Works a beaut':

HTML:

<a href="#" onclick="expandAll();"
  title="Expand All" alt="Expand All"><img
    src="/images/expand-all-icon.png"/></a>
<a href="#" onclick="collapseAll();"
  title="Collapse All" alt="Collapse All"><img
    src="/images/collapse-all-icon.png"/></a>

Javascript:

async function expandAll() {
  let heads = $(".ui-accordion-header");
  heads.each((index, el) => {
    if ($(el).hasClass("ui-accordion-header-collapsed") === true)
      $(el).trigger("click");
  });
}

async function collapseAll() {
  let heads = $(".ui-accordion-header");
  heads.each((index, el) => {
    if ($(el).hasClass("ui-accordion-header-collapsed") === false)
      $(el).trigger("click");
  });
}


(The HTML newlines are placed in those weird places to prevent whitespace in the presentation.)

Nathan Hawks
  • 505
  • 4
  • 12
0

If you are ok with each panel being independent then just put each panel in it's own accordion:

$(".accordion-panel").accordion({
            collapsible: true,
            multiple: true,
            active: 0
});

Then in the html you can create each section as it's own accordion.

<div class="accordion-panel">
<h3 class="accordion-header">Section 1</h3>
    <div>
        <p>
        Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer
        ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit
        amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut
        odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.
        </p>
    </div>
</div>
<div class="accordion-panel">
<h3 class="accordion-header">Section 2</h3>
    <div>
        <p>
        Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet
        purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor
        velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In
        suscipit faucibus urna.
        </p>
    </div>
</div>