Thursday, 27 October 2016

JavaScript accordion sans jQuery

Doing some work where using jQuery was verboten this (Ridiculously simple accordion without the jQuery UI library) was found.

It still used jQuery though so rolled my own. This markup:

<div id="accordion">
    <h4 class="accordion-toggle">Accordion 1</h4>
    <div class="accordion-content default">
        <p>Cras malesuada ultrices augue molestie risus.</p>
    </div>
    <h4 class="accordion-toggle">Accordion 2</h4>
    <div class="accordion-content">
        <p>Lorem ipsum dolor sit amet mauris eu turpis.</p>
    </div>
    <h4 class="accordion-toggle">Accordion 3</h4>
    <div class="accordion-content">
        <p>Vivamus facilisisnibh scelerisque laoreet.</p>
    </div>
</div>

With this CSS:

.accordion-toggle {
    cursor: pointer;
}
.accordion-content {
    display: none;
}
.accordion-content.default {
    display: block;
}

And with this Javascript (Polyfill added):

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Polyfill
if (!Array.prototype.filter) {
    Array.prototype.filter = function(fun/*, thisArg*/) {
        'use strict';

        if (this === void 0 || this === null) {
            throw new TypeError();
        }

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== 'function') {
            throw new TypeError();
        }

        var res = [];
        var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
        for (var i = 0; i < len; i++) {
            if (i in t) {
                var val = t[i];

                // NOTE: Technically this should Object.defineProperty at
                //       the next index, as push can be affected by
                //       properties on Object.prototype and Array.prototype.
                //       But that method's new, and collisions should be
                //       rare, so use the more-compatible alternative.
                if (fun.call(thisArg, val, i, t)) {
                    res.push(val);
                }
            }
        }

        return res;
    };
}

(function() {
    "use strict";
    // Stolen from: https://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/
    var forEach = function(array, callback, scope) {
        for (var i = 0; i < array.length; i++) {
            callback.call(scope, i, array[i]);
        }
    },
        accordion = document.getElementById("accordion"),
        toggles = accordion.querySelectorAll(".accordion-toggle"),
        greedy = false;
    forEach(toggles, function(i, v) {
        v.addEventListener("click", function() {
            var content = this.nextElementSibling;
            var classes = content.className.split(" ");
            if (classes.indexOf("default") !== -1) {
                content.className = classes.filter(function(value){
                    return value !== "default"
                }).join(" ");
            } else {
                if (greedy) {
                    forEach(toggles, function(i, toggle) {
                        var ct = toggle.nextElementSibling;
                        var cs = ct.className.split(" ");
                        ct.className = cs.filter(function(value){
                            return value !== "default"
                        }).join(" ");
                    });
                }
                content.className += " default";
            }
        }, false);
    });
})();

Does all that I need except for the animation - but I'll get there yet!

Working example here.