0

I have a bootstrap 3.x accordion(collapse) on a page where I'm trying to get users to be able to add/edit a lot of information without a ton of scrolling. Some of this information is dependant on the rest so I've got an UpdatePanel within one of the accordion tabs. Either the updatpanel isn't doing its thing properly or it just doesn't play nice with the accordion in some other way.

Here's the basic accordion code:

<div class="panel-group" id="ccAccordion" role="tablist" aria-multiselectable="true">
    <asp:Panel ID="pnlAddress" runat="server" class="panel panel-default">
        <div class="panel-heading" role="tab" id="billAddressHeading">
            <h4 class="panel-title">
                <a role="button" data-toggle="collapse" data-parent="#ccAccordion" href="#billToAddressTab" aria-expanded="false" aria-controls="billToAddressTab">
                    <asp:Literal ID="BillToAddressLabelTxt" runat="server" Text="Bill Address" />
                </a>
            </h4>
        </div>
        <div id="billToAddressTab" class="panel-collapse collapse" role="tabpanel" aria-labelledby="billAddressHeading">
            <div class="panel-body">
                <!-- Other controls -->
            </div>
        </div>
    </asp:Panel>
    <div class="panel panel-default">
        <div class="panel-heading" role="tab" id="thirdPartyHeading">
            <h4 class="panel-title">
                <a role="button" data-toggle="collapse" data-parent="#ccAccordion" href="#thirdPartyTab" aria-expanded="false" aria-controls="thirdPartyTab">
                    <asp:Literal ID="ThirdPartyShippingLabelTxt" runat="server" Text="Shipping Accounts" />
                </a>
            </h4>
        </div>
        <div id="thirdPartyTab" class="panel-collapse collapse" role="tabpanel" aria-labelledby="thirdPartyHeading">
            <div class="panel-body">
                <asp:UpdatePanel runat="server" UpdateMode="Always" ChildrenAsTriggers="true">
                    <Triggers>
                        <asp:AsyncPostBackTrigger ControlID="btnAddShipAccount" EventName="Click" />
                        <asp:AsyncPostBackTrigger ControlID="btnSaveSABA" EventName="Click" />
                        <asp:AsyncPostBackTrigger ControlID="btnAddShipAccountNo" EventName="Click" />
                    </Triggers>
                    <ContentTemplate>
                                        <asp:Button ID="btnAddShipAccount" runat="server" CssClass="btn btn-success" Text="Add New Shipping Account"
                                             OnClick="btnAddShipAccount_Click" /><!-- moved from inside panel -->
                        <asp:Panel ID="pnlBranchShipping" runat="server" DefaultButton="btnSaveSABA">
                            <div class="panel panel-default">
                                <div class="controls">
                                    <div class="form-group">
                                    <!-- Other controls -->
                                    </div>
                                    <asp:panel ID="pnlAddEditAcct" CssClass="controls" runat="server" Visible="false">
                                        <!-- Other controls -->
                                        <div>
                                            <asp:LinkButton ID="btnSaveSABA" Text="Save" runat="server" ValidationGroup="vgSAB" OnClick="btnSaveSABA_Click"
                                                CssClass="btn btn-default" />
                                        </div>
                                        <div class="form-group">
                                            <asp:Button ID="btnAddShipAccountNo" runat="server" CssClass="btn btn-default" Text="Add Shipping Account" OnClick="btnAddShipAccountNo_Click"
                                                ValidationGroup="vgShipAccount" />
                                        </div>
                                    </asp:panel>
                                </div>
                            </div>
                        </asp:Panel>

                    </ContentTemplate>
                </asp:UpdatePanel>
            </div>
        </div>
    </div>
    <div class="panel panel-default">
        <div class="panel-heading" role="tab" id="vatHeading">
            <h4 class="panel-title">
                <a role="button" data-toggle="collapse" data-parent="#ccAccordion" href="#vatTab" aria-expanded="false" aria-controls="vatTab"> 
                    <asp:Literal ID="VatNumberLabelTxt" runat="server" Text="Tax Ids" />
                </a>
            </h4>
        </div>
        <div id="vatTab" class="panel-collapse collapse" role="tabpanel" aria-labelledby="vatHeading">
            <div class="panel-body">
                <asp:Panel ID="pnlTaxId" runat="server" class="form-group" DefaultButton="btnAddTaxId">
                <!-- Other controls -->
                </asp:Panel>
            </div>
        </div>
    </div>
</div>

What happens when you click any of the buttons inside is it basically refreshes the whole page and the accordion goes back to its original state, but the the things the button did (showing additional fields, for example) are visible when you open up the tab again.

Update

I realized today that the whole bit of code above is within another UpdatePanel which is in the MasterPage. This UpdatePanel has all the default settings, so the postback in my code above was triggering the parent. I'm guessing to get around this I'm going to have to change the way the whole form operates.

Matt
  • 1,213
  • 14
  • 39
  • Can you post the runtime HTML (source) instead? I know ASP, but I don't suppose you'd expect me to open VS, create a solution, create the custom controls, code the back-end, inject bootstrap, debug, and come back wth a solution, do ya? lol :) Much easier if you post the HTML in CODEPEN or JFiddle – LOTUSMS Jan 03 '16 at 00:02
  • I put it in jsfiddle but the problem didn't happen there because the postback javascript is broken due to a lack of the underlying ASP.NET code behind. Another attempt with more of my runtime markup and javascripts resulted in the postback happening but nothing came back again because of the lack of the back-end. Maybe I'm just misunderstanding how UpdatePanel is supposed to work. – Matt Jan 04 '16 at 01:50

2 Answers2

1

I ran into the same problem and used the following bit of custom code to resolve it.

(function (rc, $, undefined) {
(function (ui, $, undefined) {

    // #region Coordinator

    ui.updatePanelControlStateManagers = (function () {

        var managers = [];

        function UpdatePanelControlStateCoordinator() {
            $(function () {
                initialize(this);
            });
        }

        // #region Private

        function initialize(module) {
            Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(onBeginRequest);
            Sys.WebForms.PageRequestManager.getInstance().add_endRequest(onEndRequest);
        }

        function onBeginRequest(prm, e) {
            managers.forEach(function (m) {
                var updatePanels = e.get_updatePanelsToUpdate().map(function (v) { return $("[id$=" + v.substring(v.lastIndexOf("$") + 1) + "]"); });
                m.persistStates(updatePanels);
            });
        }

        function onEndRequest() {
            managers.forEach(function (m) {
                m.restoreStates();
            });
        }

        // #endregion

        // #region Public

        UpdatePanelControlStateCoordinator.prototype.add = function (instance) {
            managers.push(instance);
        };

        UpdatePanelControlStateCoordinator.prototype.remove = function (type) {
            var index = managers.findIndex(function (m) { return m instanceof type });

            if (index)
                managers = managers.splice(index, 1);
        };

        // #endregion

        return new UpdatePanelControlStateCoordinator();

    }());

    // #endregion

    // #region Managers

    ui.accordionControlStateManager = (function () {

        function AccordionControlStateManager() {
            this.states = [];
            this.scrollPosition = null;
        }

        // #region Public

        AccordionControlStateManager.prototype.persistStates = function (updatePanels) {
            var that = this;

            updatePanels.forEach(function (panel) {
                var $panel = $(panel);

                $panel.find(".panel-collapse").each(function (i, elem) {
                    var selector = rc.utilities.getNestedSelectorForElement(elem, $panel);
                    var expanded = $(elem).hasClass("in");

                    that.states.push({ selector: selector, expanded: expanded });
                });
            });

            this.scrollPosition = $(window).scrollTop();
        };

        AccordionControlStateManager.prototype.restoreStates = function () {
            this.states.forEach(function (state) {
                var $elem = $(state.selector);

                if ($elem.length)
                    $elem.toggleClass("in", state.expanded);
            });

            $(window).scrollTop(this.scrollPosition);

            this.scrollPosition = null;
            this.states = [];
        };

        // #endregion

        return AccordionControlStateManager;

    }());

    ui.tabsControlStateManager = (function () {

        function TabsControlStateManager() {
            this.states = [];
            this.scrollPosition = null;
        }

        // #region Public

        TabsControlStateManager.prototype.persistStates = function (updatePanels) {
            var that = this;

            updatePanels.forEach(function (panel) {
                var $panel = $(panel);

                $panel.find("ul.nav-tabs").each(function (i, elem) {
                    var selector = rc.utilities.getNestedSelectorForElement(elem, $panel);
                    var tab = $(elem).find("li.active a").attr("href");

                    that.states.push({ selector: selector, tab: tab });
                });
            });

            this.scrollPosition = $(window).scrollTop();
        };

        TabsControlStateManager.prototype.restoreStates = function () {
            this.states.forEach(function (state) {
                var $elem = $(state.selector);

                if ($elem.length)
                    $elem.find("li > a[href='" + state.tab + "']").tab("show");
            });

            $(window).scrollTop(this.scrollPosition);

            this.scrollPosition = null;
            this.states = [];
        };

        // #endregion

        return TabsControlStateManager;

    }());

    // #endregion

}(rc.ui = rc.ui || {}, $));
}(window.rc = window.rc || {}, jQuery));

Which references the below utility js:

(function (rc, $, undefined) {
(function (utilities, $, undefined) {
    utilities.getSelectorForElement = function($elem) {
        var result = null,
            id = $elem.attr("id"),
            className = $elem[0].className,
            tagName = $elem[0].tagName;

        if (id) {
            result = "#" + id;
        } else if (className) {
            result = "." + className.replace(/\s/g, ".");
        } else {
            result = tagName;
        }

        if ($elem.parent().children(result).length > 1) {
            result = result + ":eq(" + $elem.index() + ")";
        }

        return result;
    }

    utilities.getNestedSelectorForElement = function (elem, $recurseTo) {
        var $elem = $(elem);

        if ($elem.is($recurseTo)) {
            return "";
        }

        var selector = utilities.getSelectorForElement($elem);
        var parentSelector = utilities.getNestedSelectorForElement($elem.parent(), $recurseTo);

        selector = parentSelector ? parentSelector + " > " + selector
                                  : selector;

        return selector;
    }

}(rc.utilities = rc.utilities || {}, $));
}(window.rc = window.rc || {}, jQuery));

And is initialized like so:

$(function() {
    rc.ui.updatePanelControlStateManagers.add(new rc.ui.accordionControlStateManager());
    rc.ui.updatePanelControlStateManagers.add(new rc.ui.tabsControlStateManager());
});

The idea being that you can create a custom state manager for any controls which are causing you trouble (tab and accordion for me), wire it up in your initialization logic, and it'll get handled in any update panel.

dshapiro
  • 376
  • 2
  • 12
  • Wow that's way more complicated than the actual solution I used... I ended up finding something. I'll post a link in my comments. – Matt Apr 01 '16 at 20:17
  • It's really not that bad. I wanted an extensible system that allowed me to create multiple state managers (and it paid off because I ended up needing this for both tabs and accordions). Otherwise the logic for just accordions could be extracted and it'd be much smaller. Please do share what you've got though. – dshapiro Apr 02 '16 at 23:38
  • Added an answer with my solution. – Matt Apr 04 '16 at 15:39
1

I actually ended up using the solution in THIS answer: https://stackoverflow.com/a/25258290/727857

Again this is for Bootstrap 3.x.x and requires jquery-cookie.js from https://github.com/carhartl/jquery-cookie

Edited to add the full text of the answer above in case the link dies:

Sample HTML for accordion:

<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 in">
            <div class="panel-body">
                Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson
                ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food
                truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put
                a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim
                keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
                Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table,
                raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus
                labore sustainable VHS.
            </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. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food
                truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put
                a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim
                keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
                Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table,
                raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus
                labore sustainable VHS.
            </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. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food
                truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put
                a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim
                keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
                Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table,
                raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus
                labore sustainable VHS.
            </div>
        </div>
    </div>
</div>

Javascript (jquery) to retain the state of the tabs in this accordion:

$(document).ready(function () {
        //when a group is shown, save it as the active accordion group
        $("#accordion").on('shown.bs.collapse', function () {
            var active = $("#accordion .in").attr('id');
            $.cookie('activeAccordionGroup', active);
          //  alert(active);
        });
        $("#accordion").on('hidden.bs.collapse', function () {
            $.removeCookie('activeAccordionGroup');
        });
        var last = $.cookie('activeAccordionGroup');
        if (last != null) {
            //remove default collapse settings
            $("#accordion .panel-collapse").removeClass('in');
            //show the account_last visible group
            $("#" + last).addClass("in");
        }
    });

I don't know whether dshapiro's answer will work or is better, but this is less javascript and could easily be made into a function which is repeatable on different pages.

Community
  • 1
  • 1
Matt
  • 1,213
  • 14
  • 39
  • Whilst this may theoretically answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Bhargav Rao Apr 04 '16 at 15:43
  • This would be a good solution if you needed to persist the state between page visits as it uses cookies. Probably not a typical requirement when dealing with accordions in update panels, but it could be. – dshapiro Apr 04 '16 at 16:36
  • Corrected to include the text from the original answer. Was not trying to take credit for someone else's work. – Matt Apr 04 '16 at 16:53
  • I don't but this was quick, effective, existed and didn't require a ton of coding. The cookie will expire at the end of the user's browser session. – Matt Apr 04 '16 at 16:59