Responsive Horizontal Accordion for Bootstrap's Collapse

UPDATE November 16, 2016

Panel's width needs to be 100% when its collapse panel is expanded. Therefore, there are additional changes to this post:

  1. First expanded panel has style="width: 100%;" inline
  2. When the header is clicked, then this style needs to be toggled, so additional Javascript function is needed because the selector is traversal to the parent element.

Twitter's Bootstrap is the the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web. Almost every new websites are using this framework for quick design and bulletproof responsive/mobile friendly layout.

One of its feature is a collapse/accordion plugin. But too bad, it doesn't provide horizontal way to do the slider.

After looking all google results, I ended up writing my own code to fit my needs: a horizontal slider, which **will be reverted back** to original collapse panel in mobile screen, without hacking the plugin's javascript core file.

There are some key points to be noticed:

  1. To make the panels stacked horizontally, I use Flexbox.
  2. Headings should be rotated.
  3. On horizontal layout, the slider must be animated horizontally as well.
  4. Switched back to vertical layout on smaller device size (less than 992 pixels wide).

So, here's the example:

HTML

<!-- start horizontal -->
<div
    class="panel-group horizontal"
    id="accordion"
    role="tablist"
    aria-multiselectable="true"
    >
    <div class="panel" style="width: 100%;">
        <div class="panel-heading" role="tab" id="headingOne">
            <h4 class="panel-title">
                <a
                    role="button"
                    data-toggle="collapse"
                    data-parent="#accordion"
                    href="#collapseOne"
                    aria-expanded="true"
                    aria-controls="collapseOne"
                    >
                    Collapsible Group Item #1
                </a>
            </h4>
        </div>
        <div
            id="collapseOne"
            class="panel-collapse collapse in"
            role="tabpanel"
            aria-labelledby="headingOne"
            >
            <div class="panel-body">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla suscipit at orci eget egestas. Nam sed diam id magna sagittis interdum. Donec malesuada maximus nisl, at interdum nibh bibendum quis. Mauris scelerisque massa ut efficitur hendrerit. Vestibulum venenatis dolor nibh, accumsan finibus libero efficitur ut. Phasellus sed congue urna. Maecenas blandit ultricies dolor, et dignissim nisl consectetur non. Aliquam erat volutpat. Aenean id tellus egestas eros consequat fermentum at a ex. Praesent porta ipsum at lorem ultricies maximus. Vivamus ut finibus velit. Nunc id dolor pharetra, egestas arcu sed, scelerisque libero. Cras fringilla eros non magna auctor, nec tincidunt erat hendrerit. Nullam nec pretium leo. Praesent consectetur magna tortor, at luctus metus porttitor vel. Suspendisse potenti.
            </div>
        </div>
    </div>
    <div class="panel">
        <div class="panel-heading" role="tab" id="headingTwo">
            <h4 class="panel-title">
                <a
                    class="collapsed"
                    role="button"
                    data-toggle="collapse"
                    data-parent="#accordion"
                    href="#collapseTwo"
                    aria-expanded="false"
                    aria-controls="collapseTwo"
                    >
                    Collapsible Group Item #2
                </a>
            </h4>
        </div>
        <div
            id="collapseTwo"
            class="panel-collapse collapse"
            role="tabpanel"
            aria-labelledby="headingTwo"
            >
            <div class="panel-body">
                Fusce non ex in sapien tristique consectetur. Sed sed massa id purus elementum scelerisque. In quam dui, tristique id pretium sed, pulvinar eu dui. Fusce nec elit felis. Integer rutrum lorem eros, in placerat mauris sollicitudin sed. Integer elementum orci massa, id ullamcorper lacus tempus id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
            </div>
        </div>
    </div>
    <div class="panel">
        <div class="panel-heading" role="tab" id="headingThree">
            <h4 class="panel-title">
                <a
                    class="collapsed"
                    role="button"
                    data-toggle="collapse"
                    data-parent="#accordion"
                    href="#collapseThree"
                    aria-expanded="false"
                    aria-controls="collapseThree"
                    >
                    Collapsible Group Item #3
                </a>
            </h4>
        </div>
        <div
            id="collapseThree"
            class="panel-collapse collapse"
            role="tabpanel"
            aria-labelledby="headingThree"
            >
            <div class="panel-body">
                Duis eu laoreet ex. Sed maximus a ipsum at venenatis. Donec vel pretium justo, sed dignissim odio. Aliquam erat volutpat. Nunc varius lorem lacus, a faucibus ante malesuada eu. Praesent luctus nisl et eleifend pellentesque. Pellentesque eu orci ipsum. Aliquam eu sapien dui. In convallis neque fringilla condimentum elementum. Nam porttitor sodales diam, eu efficitur nisl posuere vitae. Ut vestibulum dui eu libero lobortis, id hendrerit libero convallis.
            </div>
        </div>
    </div>
</div>
<!-- end horizontal -->

Notice the additional "horizontal" class in the container element and nothing else to be added in this HTML part!

Javascript

Although I wanted to avoid Javascript, but turned out javascript plays an important role to inject an important CSS selector of the plugin to make it slide horizontally. It is the keyword "width" class name that is needed in the plugin to trigger a different sliding behavior.

function horizontalAccordion() {
    var panels = $('.panel-group.horizontal').find('.collapse');
    if ($(window).width() <= 992) {
        $.each(panels, function(idx, item){
            $(item).removeClass('width');
            $(item).addClass('height');
            $(item).css({
                'height': '',
                'width': ''
            });
        });
        return;
    } else {
        $.each(panels, function(idx, item){
            $(item).removeClass('height');
            $(item).addClass('width');
            $(item).css({
                'height': '',
                'width': ''
            });
        });
        expandFullWidth();
        var panels = $('.panel-group.horizontal').find('.panel');
        panels.unbind('shown.bs.collapse', expandFullWidth);
        panels.on('shown.bs.collapse', expandFullWidth);
    }
}

In this function, the width class will be added to the panel-collapse element when the browser's width is less than/equal to 992 pixels, when the bootstrap's layout will be stacked vertical globally, and will be reverted back otherwise. The case is when the visitor uses tablet and rotates it at anytime.

In my case, I also added another function, to prevent the panel collapsed when its own heading is clicked. I just did not want to have an empty horizontal area because all panels are originally collapsible.


/**
 * Prevent self link to collapse its panel
 *
 * @returns {undefined}
 */
function preventSelfCollapsed() {
    var links = $('.panel-group.horizontal a[data-toggle="collapse"]');
    $(links).unbind('click', this);
    links.on('click', function (event) {
        var _this = $(this);
        var panel = $(_this[0].hash);
        if (panel.length && panel.hasClass('in')) {
            _this.prop('disabled', true);
            _this.addClass('disabled');
            return false;
        } else {
            _this.prop('disabled', false);
            _this.removeClass('disabled');
            return true;
        }
    });
}

/**
 * Forced the panel that has .collapse .in to be 100% width and remove the others
 * This needs javascript because the selector is the parent
 *
 * @returns {undefined}
 */
function expandFullWidth(object) {
    var panels = $(this).closest('.panel-group.horizontal').find('.panel');
    $.each(panels, function(item, panel) {
        $(panel).css('width', '');
        var collapsePanel = $(panel).find('.collapse.in.width');
        if (collapsePanel) {
            collapsePanel.closest('.panel').width('100%');
        }
    });
}

So these functions are called respectively like this

$(document).ready(function () {
    horizontalAccordion();
    preventSelfCollapsed();
});

$(window).resize(function () {
    horizontalAccordion();
});

CSS

/****************************************************/
/* horizontal bootstrap's accordion ROTATIONS */
@media only screen and (min-width : 992px) {
    .panel-group.horizontal .panel {
        margin-bottom: 0;

        height: 200px; /* MANUALLY SPECIFIED HERE! */
    }
    .panel-group.horizontal .panel + .panel {
        margin-top: 0;
    }
    .panel-group.horizontal {
        position: relative;
        clear: both;
        width: 100%;
        overflow: hidden;

        display: -webkit-box;      /* OLD - iOS 6-, Safari 3.1-6 */
        display: -moz-box;         /* OLD - Firefox 19- (buggy but mostly works) */
        display: -ms-flexbox;      /* TWEENER - IE 10 */
        display: -webkit-flex;     /* NEW - Chrome */
        display: flex;             /* NEW, Spec - Opera 12.1, Firefox 20+ */

        -webkit-flex-flow: row nowrap;
        -moz-flex-flow: row nowrap;
        -o-flex-flow: row nowrap;
        flex-flow: row nowrap;
    }
    .panel-group.horizontal .panel {
        display: -webkit-box;      /* OLD - iOS 6-, Safari 3.1-6 */
        display: -moz-box;         /* OLD - Firefox 19- (buggy but mostly works) */
        display: -ms-flexbox;      /* TWEENER - IE 10 */
        display: -webkit-flex;     /* NEW - Chrome */
        display: flex;             /* NEW, Spec - Opera 12.1, Firefox 20+ */
    }
    .panel-group.horizontal .panel .panel-heading {
        display: inline-block;
        padding: 0;
        margin: 10px;

        width: 1.5em;
        line-height: 1.5;
    }
    .panel-group.horizontal .panel .panel-title {
        display: inline-block;
        height: auto;
        white-space: nowrap;
        transform: translate(0,100%) rotate(-90deg);
        transform-origin: left top;
    }
    .panel-group.horizontal .panel .panel-title:after {
        content: "";
        float: left;
        margin-top: 100%;
    }
    .panel-group.horizontal .panel .collapse {
        overflow-x: hidden;
        overflow-y: auto;
    }
}

/****************************************************/
.panel-group.horizontal .collapse {
    display: none;
    visibility: hidden;
}
.panel-group.horizontal .collapse.in {
    display: block;
    visibility: visible;
    width: 100%;
}
.panel-group.horizontal tr.collapse.in {
    display: table-row;
}
.panel-group.horizontal tbody.collapse.in {
    display: table-row-group;
}
.panel-group.horizontal .collapsing.width {
    -webkit-transition-property: width, visibility;
    -moz-transition-property: width, visibility;
    -o-transition-property: width, visibility;
    transition-property: width, visibility;

    width: 0;
    height: auto;
}

Make sure you modify the "height" element of the ".panel-group.horizontal .panel" class name, as it must be a fixed height.

And that's it. Copy paste these codes, and try it out yourself.

Example

For a quick look, here's the working example.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla suscipit at orci eget egestas. Nam sed diam id magna sagittis interdum. Donec malesuada maximus nisl, at interdum nibh bibendum quis. Mauris scelerisque massa ut efficitur hendrerit. Vestibulum venenatis dolor nibh, accumsan finibus libero efficitur ut. Phasellus sed congue urna. Maecenas blandit ultricies dolor, et dignissim nisl consectetur non. Aliquam erat volutpat. Aenean id tellus egestas eros consequat fermentum at a ex. Praesent porta ipsum at lorem ultricies maximus. Vivamus ut finibus velit. Nunc id dolor pharetra, egestas arcu sed, scelerisque libero. Cras fringilla eros non magna auctor, nec tincidunt erat hendrerit. Nullam nec pretium leo. Praesent consectetur magna tortor, at luctus metus porttitor vel. Suspendisse potenti.
Fusce non ex in sapien tristique consectetur. Sed sed massa id purus elementum scelerisque. In quam dui, tristique id pretium sed, pulvinar eu dui. Fusce nec elit felis. Integer rutrum lorem eros, in placerat mauris sollicitudin sed. Integer elementum orci massa, id ullamcorper lacus tempus id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
Duis eu laoreet ex. Sed maximus a ipsum at venenatis. Donec vel pretium justo, sed dignissim odio. Aliquam erat volutpat. Nunc varius lorem lacus, a faucibus ante malesuada eu. Praesent luctus nisl et eleifend pellentesque. Pellentesque eu orci ipsum. Aliquam eu sapien dui. In convallis neque fringilla condimentum elementum. Nam porttitor sodales diam, eu efficitur nisl posuere vitae. Ut vestibulum dui eu libero lobortis, id hendrerit libero convallis.

References

  1. A Visual Guide to CSS3 Flexbox Properties
  2. Rotated text

 

Happy coding!



Comments

blog comments powered by Disqus