jQuery UI Accordion Expand/Collapse All
Asked Answered
B

12

36

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/

Beatify answered 11/10, 2012 at 15:40 Comment(0)
B
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/

Beatify answered 17/10, 2012 at 14:8 Comment(2)
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; });`Ease
What if there is a nested accordion? Will this code work?Eau
B
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

Bautista answered 15/10, 2012 at 19:58 Comment(13)
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.Beatify
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.jsBautista
Many thanks. Was interesting to see if this was possible, thanks for your replies.Beatify
One last question: How would I reproduce the first item being open, like jQuery UI Accordion does by default?Beatify
by adding .first().show() to the line which initially hides all of the content panels. I updated the final example.Bautista
That seems to make the panels un-collapsible. It's OK, have solved with this hybrid solution.Beatify
Your example does not work fo ALL elements. Just check it.Eau
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.Bautista
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!Incommunicado
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?Rammish
I'm not sure I understand @Rammish 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.Bautista
That's true. By default that is the behavior, but in that fiddle, it doesn't behave that wayRammish
@Bautista Thank you so much! Wow. That really helped. I was not expecting a response for an old post. But, thank you!Rammish
C
21

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

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

JSFiddle

Cauliflower answered 28/11, 2015 at 0:30 Comment(3)
You beat me to the punch. I accidentally discovered this myself.Huck
$(".ui-accordion-content").slideDown(300);Hagiocracy
$(".ui-accordion-content").toggle(); //enables show/hide behavior. The only downside is any opens will be closed and vice versa.Winsome
P
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/

Privity answered 5/4, 2013 at 16:38 Comment(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?Blanketyblank
I had the same problem as @BlanketyblankSantonin
same here. for some reason it does not close on first click.Paulettepauley
Same here, needs two clicks to closeVerwoerd
B
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/

Beatify answered 17/10, 2012 at 14:8 Comment(2)
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; });`Ease
What if there is a nested accordion? Will this code work?Eau
U
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().

Unplumbed answered 11/10, 2012 at 15:55 Comment(4)
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 :)Beatify
Unfortunately the "only 1 open" functionality is fundamental 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.Bautista
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?Beatify
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.Bautista
A
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.

Amin answered 24/2, 2014 at 5:52 Comment(0)
R
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();
   }
Rosary answered 24/2, 2014 at 16:3 Comment(0)
L
1

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>
Lillalillard answered 10/3, 2021 at 18:47 Comment(0)
H
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>
Helwig answered 15/1, 2014 at 11:10 Comment(0)
Y
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;
});
Yorick answered 18/6, 2014 at 13:33 Comment(0)
C
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();
});
Chiekochien answered 21/4, 2017 at 5:34 Comment(0)
E
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.)

Earthenware answered 8/12, 2019 at 2:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.