21

I'm trying to determine how I might save a collapsible panel's collapsed state using $.cookie.

This question has been helpful so far, but still missing the end solution.

Any solutions I have found so far have only saved the last rolled down panel so when the page is reloaded the only panel saved is the last one.

What I need is to save all panels that are rolled down rather than just one.

Link to jCookie plugin on Github.

Link to demo on JSFiddle


UPDATE

It has been suggested that LocalStorage is a more appropriate solution to what I am trying to achieve. If you can comment on why and what local storage is that would be much appreciated.


UPDATE 2

because of the suggestion that local storage would be an improvement over using cookies for this problem. The selected answer was based off this. However as mentioned by Robin, there are downsides to using this technique on HTTPS sites.


HTML

<div class="panel panel-default">
    <div data-toggle="collapse" data-target="#panel1" class="panel-heading collapsed">
        <h4 class="panel-title">
            <a> Panel 1 </a>
        </h4>
    </div>
    <div id="panel1" class="panel-collapse collapse">
        <div class="panel-body">
        </div>
    </div>
</div>


<div class="panel panel-default">
    <div data-toggle="collapse" data-target="#panel2" class="panel-heading collapsed">
        <h4 class="panel-title">
            <a> Panel 2 </a>
        </h4>
    </div>
    <div id="panel2" class="panel-collapse collapse">
        <div class="panel-body">
        </div>
    </div>
</div>


<div class="panel panel-default">
    <div data-toggle="collapse" data-target="#panel3" class="panel-heading collapsed">
        <h4 class="panel-title">
            <a> Panel 3 </a>
        </h4>
    </div>
    <div id="panel3" class="panel-collapse collapse">
        <div class="panel-body">
        </div>
    </div>
</div>

jQUERY

$(".panel .panel-collapse").on('shown.bs.collapse', function ()
{
    var active = $(this).attr('id');
    $.cookie('activePanelGroup', active);
});

$(".panel .panel-collapse").on('hidden.bs.collapse', function ()
{
    $.removeCookie('activePanelGroup');
});

var last = $.cookie('activePanelGroup');
if (last != null)
{
    //remove default collapse settings
    $(".panel .panel-collapse").removeClass('in');
    //show the account_last visible group
    $("#" + last).addClass("in");
}
Community
  • 1
  • 1
Master Yoda
  • 4,334
  • 10
  • 43
  • 77
  • You need to create a cookie for each panel or store an array of the active panels. – Cobote Nov 04 '15 at 14:29
  • Yeah my original thought was to create an array of active panels but im not sure how to iterate over the cookie array as there is no each() function associated and it returns undefined with a simple for loop. – Master Yoda Nov 05 '15 at 08:47
  • 1
    I would definitely use localStorage or sessionStorage. I will try and find an example. Also you are calling the same jquery selectors several times, it will speed things up if you store them in a variable then they are cached. For example: var trigger = $(".panel .panel-collapse"); then trigger.on('click') etc etc. – lharby Nov 06 '15 at 09:29
  • Thanks Iharby. I wasnt aware of local storage, i think in this instance it might be the better option. – Master Yoda Nov 06 '15 at 09:57

5 Answers5

27

This will create a cookie for every panel when it's shown and remove the cookie when the panel is hidden.

$(".panel .panel-collapse").on('shown.bs.collapse', function ()
{
    var active = $(this).attr('id');
    $.cookie(active, "1");
});

$(".panel .panel-collapse").on('hidden.bs.collapse', function ()
{
    var active = $(this).attr('id');
    $.removeCookie(active);
});

So, when loading the document, we check every cookie and expand the panel.

$(document.ready(function(){
    var panels=$.cookie(); //get all cookies
    for (var panel in panels){ //<-- panel is the name of the cookie
        if ($("#"+panel).hasClass('panel-collapse')) // check if this is a panel
        {
            $("#"+panel).collapse("show");
        }
    }    
});

USING LOCALSTORAGE

However, as someone suggested, using localStorage may be a better option. localStorage is great for this.

$(".panel .panel-collapse").on('shown.bs.collapse', function ()
{
    var active = $(this).attr('id');
    var panels= localStorage.panels === undefined ? new Array() : JSON.parse(localStorage.panels);
    if ($.inArray(active,panels)==-1) //check that the element is not in the array
        panels.push(active);
    localStorage.panels=JSON.stringify(panels);
});

$(".panel .panel-collapse").on('hidden.bs.collapse', function ()
{
    var active = $(this).attr('id');
    var panels= localStorage.panels === undefined ? new Array() : JSON.parse(localStorage.panels);
    var elementIndex=$.inArray(active,panels);
    if (elementIndex!==-1) //check the array
    {
        panels.splice(elementIndex,1); //remove item from array        
    }
    localStorage.panels=JSON.stringify(panels); //save array on localStorage
});

When you load the page, get the values of localStorage and show the panels.

$(document.ready(function(){
    var panels=localStorage.panels === undefined ? new Array() : JSON.parse(localStorage.panels); //get all panels
    for (var i in panels){ //<-- panel is the name of the cookie
        if ($("#"+panels[i]).hasClass('panel-collapse')) // check if this is a panel
        {
            $("#"+panels[i]).collapse("show");
        }
    }  
});

EDIT: See it working: FIDDLE

ojovirtual
  • 3,332
  • 16
  • 21
  • Hi I tested this for local storage, its not working. Could you provide an example so that i know im not just placing code in the wrong order? Thanks – Master Yoda Nov 06 '15 at 09:55
  • @KyleT I have updated my solution, there were some errors. I have added a Fiddle link. – ojovirtual Nov 06 '15 at 10:10
  • Still seems to be an issue, Check the Fiddle again. If i open all panels. Reload its fine. If i then close one panel and reload it still opens all panels. – Master Yoda Nov 06 '15 at 10:30
  • @KyleT Aaaargh! Confused splice with slice. Check again. Now it works. Updated fiddle link. – ojovirtual Nov 06 '15 at 10:33
  • ``$(document.ready(function(){`` should be ``$(document).ready(function(){`` – Dzhuang Nov 19 '16 at 14:53
3

You should save the active panels in an array and store it in a cookie instead saving only 1 id at a time.

Your JS should be like this:

var activePanels = []; // array for the active panels
$(".panel .panel-collapse").on('shown.bs.collapse', function ()
{   
    var active = $(this).attr('id');
    activePanels.push(active); // store the active panel id into the array
    $.cookie('activePanelGroup', activePanels); // add array to the cookie
    console.log($.cookie('activePanelGroup')); // display current active panels stored in the cookie
});

$(".panel .panel-collapse").on('hidden.bs.collapse', function ()
{
    var selectedPanel = $(this).attr('id');
    var index = activePanels.indexOf(selectedPanel);
    if (index > -1) // if id exists on the active panels array
      activePanels.splice(index, 1); // remove it on the active panels array
    $.cookie('activePanelGroup', activePanels); // add the updated active panels array to remove the old one
    console.log($.cookie('activePanelGroup')); // display current active panels stored in the cookie
});

var aPanels = $.cookie('activePanelGroup');  // retrieve all active panels 

if (aPanels != null)
{   
    //remove default collapse settings
    $(".panel .panel-collapse").removeClass('in');
    // put all active ids to array
    var arr = aPanels.split(",");
    // loop on each active panels
    $.each( arr, function( key, value ) {
      //show the account_last visible group
      $("#" + value).addClass("in");

      // store again the selected panels so it won't get reset on reload
      activePanels.push(value);
      $.cookie('activePanelGroup', activePanels);
    });
}

Fiddle

Robin Carlo Catacutan
  • 13,249
  • 11
  • 52
  • 85
  • Hi Robin, this is a viable solution for JQuery.cookie. It solves the problem. However based on the suggestions made by other SO users i would now prefer a local storage based approach. Whats your opinion? Would it be a more suitable solution? – Master Yoda Nov 06 '15 at 09:58
  • @KyleT Yes, localstorage is more suitable for this solution as its much faster since the cookies will send the data on each http header. However there's a bit downside, you can't access the same value you stored in the localstorage set on http if you're in an https page and vice versa. – Robin Carlo Catacutan Nov 06 '15 at 10:13
1

You can have the settings in a single cookie (i.e. comma separated) and update it on open-close.

openPanels = $.cookie('activePanelGroup').split(",");

See your edited JsFiddle example working here.

EDIT

I agree with Chris. I find it a more elegant solution to have all values stored in a single cookie, however, we should keep in mind the single cookie size limitations. (This varies based on browser).

DDan
  • 8,068
  • 5
  • 33
  • 52
  • Better than storing loads of cookies, but should come with the health warning that max cookie size is around 4,000 characters so if you've lots of collapsing divs and/or they have long IDs this might hit that limit. – Chris Aug 07 '17 at 23:55
1

<!-- when you have multiple levels of child-sections like this below - to hide/collapse or show and restore again -->
<!--here's how I kept track of their id's - ps. I have since removed the multiple -->
<!--tables but, it looked like this when I put this answer in -->

    <div class="panel-group" id="accordion">
        <div class="panel panel-default">
            <div class="panel-heading">
            </div>
            <div class="panel-body">
<!--TABLE 1-->
                <table class="table table-condensed" style="border-collapse: collapse;">
                    <tbody class="component-grp">
                        <tr data-toggle="collapse" data-parent="#accordion" data-target="#compogrp@x" class="accordion-toggle">
                            <td colspan="10">
                                <div class="">
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <td class="hiddenRow">
                                <div class="accordian-body panel-collapse collapse" id="compogrp@x">
<!--TABLE 2-->
                                    <table class="table table-striped">
                                        <thead>
                                            <tr class="header">
                                                <th></th>
                                                <th>Position</th>
                                                <th>shape</th>

                                            </tr>
                                        </thead>
                                        <tbody class="component-row">
                                            <tr data-toggle="collapse" data-target="#comp-@x" class="accordion-toggle collapsed">
                                                <td>
                                                </td>
                                            </tr>
                                            <tr>
                                                <td colspan="10" class="hiddenRow">                                                
                                                </td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>

        </div>

    </div>

Let me throw in an additional alternative: if you have customized the bootstrap panel example like me and ended up with multiple nesting for your accordion,then assuming you have unique ids for your collapsing & expanding element (the panel that expands or collapses), you can trap the (rely on) click event to get the id of the panel collapsing/expanding no matter how deep it's located in the nesting.

I customized the examples in stackoverflow to achieve this; basically it gets the id of the expanded panel on click event, creates a cookie with the id as name (var temp) which will be used to remove it later if panel is collapsed. Then loop through cookie on page refresh (or etc.) to restore the accordion state. I must mention that cookies are not recommended for this type of spec. Thus, you can change the code to use localStorage or Javascript array. Hope this helps someone.

    $(document).on('shown.bs.collapse', function (e) {
           //whenever and wherever a panel is shown on any level of the  nesting, save the id in a cookie
            var active = $(e.target).attr('id')
           var temp = active;
            $.cookie(temp, active);
            console.log("panel expanded: ");
        });

        $(document).on('hidden.bs.collapse', function (e) {
            var temp = $(e.target).attr('id')
           //whenever and wherever a panel is collapsed on any level of the  nesting, remove the corresponding cookie
            $.removeCookie(temp);
            console.log("panel collapsed: ");

        });

        function restoreActiveAccordionGroup() {
             var last = [];
             last = $.cookie();
            if (last) {

                //remove default collapse settings from all panels
                $("#accordion").removeClass('in');
                for (var i in last) {
                //restore the last visible panel group in all nested levels
                $("#" + i).addClass("in");
                }

            }
        }
user3590235
  • 153
  • 7
0

I'd rather use localStorage than cookies.

You just need to create a variable on local storage for each panel associated to it's id, where you store the state of the panel.

When loading the page, just loop all panel classed objects checking it's associated variable in local storage and applying appropiate classes depending on the value.

Bardo
  • 2,470
  • 2
  • 24
  • 42