dropdown menu

by slawe

HTML

<div class="dropdown" data-directive="dropdown">
    <span data-behaviour="label" id="foo">Menu</span>
    <div data-behaviour="menu">
        <ul>
            <li>
                <a data-behaviour="option" href="">Option 1</a>
            </li>
            <li>
                <a data-behaviour="option" href="">Option 2</a>
            </li>
            <li>
                <a data-behaviour="option" href="">Option 3</a>
            </li>
        </ul>
    </div>
</div>

CSS

body {
    font:normal 1.25em/1.5 Arial, sans-serif;
}
:focus {
    outline:1px dotted #EF9600;
    outline-offset:1px;
}

/* Dropdown widget */
.dropdown {
	width:10em;
	border:1px solid #CCC;
}

.dropdown a,
.dropdown > span {
	display:block;
	padding:.25em .5em;
	text-decoration:none;
	color:#000;
	background-color:#FFF;
}

.dropdown.is-closed > span:after {
	content:' \21D2';
}
.dropdown.is-open > span:after {
	content:' \21D3';
}

.dropdown a:hover,
.dropdown a:focus {
	color:#07B4BA;
	background-color:#EEE;
}

.dropdown ul {
	margin:0;
	padding:0;
	list-style-type:0;
}

JavaScript

(function (name, context, definition) {
    if (typeof define === 'function' && define.amd) {
        define(definition);
    }
    else if (typeof module !== 'undefined' && module.exports) {
        module.exports = definition();
    }
    else {
        context[name] = definition();
    }
})('Dropdown', this, function() {

    /**
     * Dropdown
     * @description Dropdown menus. Keyboard accessible with ARIA hints.
     * @constructor
     * @param element
     */
    var Dropdown = function(element) {
        this.target = element;
        this.label = element.querySelector('[data-behaviour=label]');
        this.menu = element.querySelector('[data-behaviour=menu]');
        this.options = element.querySelectorAll('[data-behaviour=option]');

        if (this.open === undefined) {
            this._init();
        }
    };

    /**
     * Init
     * @private
     */
    Dropdown.prototype._init = function() {
        var i, len;
        var self = this;

        this.target.className+= ' is-closed';

        this.label.style.cursor = 'pointer';
        this.label.setAttribute('tabindex', 0);
        this.label.setAttribute('role', 'button');
        this.label.setAttribute('aria-expanded', false);

        this.listener = function(e) {
            var isMouse = e.type === 'click';
            var isTouch = e.type === 'touchend' && e.changedTouches.length === 1;
            var isKeyboard = e.type === 'keyup' && e.keyCode === 13;

            if (isMouse || isTouch || isKeyboard) {

                if (e.preventDefault) {
                    e.preventDefault();
                }
                else {
                    e.returnValue = false;
                }

                self.toggle();
            }
        };

        var events = ['click', 'touchend'];

        // If the label is 'clickable' then keyboard support should be implied
        // <http://bit.ly/Zgip9P>
        var nodeType = this.label.tagName.toLowerCase();

        if (nodeType !==...