Dropdown

An Accessibility-enabled dropdown list with custom checkmarks

by Kyle Falconer

HTML

<div>something before</div>
<div>
    <div class="filterGroup" onclick="toggleFilterGroup(this)">
        <div role="button" aria-pressed="false" aria-haspopup="true">Role<span class="indicator_triangle"></span>

        </div>
        <ul aria-expanded="false">
            <li role="checkbox" data-name="PrimaryRole" data-value="Faculty">Faculty</li>
            <li role="checkbox" data-name="PrimaryRole" data-value="Staff">Staff</li>
            <li role="checkbox" data-name="PrimaryRole" data-value="Student">Student</li>
        </ul>
    </div>
    <div class="filterGroup" onclick="toggleFilterGroup(this)">
        <div role="button" aria-haspopup="true">Department<span class="indicator_triangle"></span>

        </div>
        <ul aria-expanded="false">
            <li role="checkbox" data-name="PrimaryRole" data-value="Admissions">Admissions</li>
            <li role="checkbox" data-name="PrimaryRole" data-value="Computer Science">Computer Science</li>
            <li role="checkbox" data-name="PrimaryRole" data-value="Relations">Relations</li>
        </ul>
    </div>
</div>
<div>something after</div>

CSS

div {
    display:block;
    min-height:50px;
}
/*
div::after {
    content:' ';
    display:block;
    height:0;
    width:0;
    clear:both;
    float:none;
}*/
 #SearchFilterWrapper {
    overflow: visible;
}
#SearchFilterWrapper.grid_whole {
    margin-bottom: 0;
}
.filterGroup {
    float:left;
}
.filterGroup *[role="button"] {
    display:inline-block;
    min-height: 25px;
    min-width:40px;
    cursor: pointer;
    overflow:hidden;
    color:#80151a;
    margin-right: 120px;
}
.filterGroup ul {
    height:0;
    margin:0;
    padding: 0;
    border:none;
    position:absolute;
    z-index: 200;
    transition:height 1s, border 1s, padding 1s;
    -webkit-transition:height 0.4s, border 0.4s, padding 0.4s;
    overflow:hidden;
    list-style:none;
    background-color:#ffffff;
    -webkit-box-shadow: 4px 4px 7px -2px rgba(0, 0, 0, 0.75);
    -moz-box-shadow: 4px 4px 7px -2px rgba(0, 0, 0, 0.75);
    box-shadow: 4px 4px 7px -2px rgba(0, 0, 0, 0.75);
}
.filterGroup.Open ul {
    height:100px;
    padding: 0;
    border: 1px solid #cfcfcf;
}
.indicator_triangle {
    display:inline-block;
    width: 0px;
    height: 0px;
    border-style: solid;
    border-width: 0 5px 8.7px 5px;
    border-color: #80151a transparent #80151a transparent;
    -webkit-transform:rotate(360deg);
    border-style: solid;
    margin-left: 10px;
}
.Open .indicator_triangle {
    border-width: 8.7px 5px 0 5px;
}
.filterGroup li[role="checkbox"] {
    display:block;
    margin:0;
    padding:4px 10px 4px 4px;
    cursor: pointer;
}
.filterGroup li[role="checkbox"]:hover {
    background-color:#efefef;
}
.filterGroup li[role="checkbox"]:before {
    display:inline-block;
    margin-right:5px;
    width:15px;
}
.filterGroup li[aria-checked="false"]:before {
    content:' ';
}
.filterGroup li[aria-checked="true"]:before {
    content:'\2713';
}

JavaScript

var Utils = Utils || {};

/**
 * A more compatible way of attaching event listeners.
 * Works on IE 7+
 * Note that if Element.prototype.addEventListener is not supported,
 *   useCapture will be ignored.
 * @see http://stackoverflow.com/a/12949687/940217
 * @param {Element} ele
 * @param {string} type
 * @param {function(?)} handler
 * @param {boolean=} useCapture
 * @return {undefined}
 */
Utils.addEventListener = function (ele, type, handler, useCapture) {
    useCapture = useCapture || false;
    if (ele.addEventListener) {
        ele.addEventListener(type, handler, useCapture);
    } else if (ele.attachEvent) {
        ele.attachEvent("on" + type, handler);
    } else {
        ele["on" + type] = handler;
    }
};

/**
 * A more compatible way of removing event listeners.
 * Works on IE 7+.
 * Note that if Element.prototype.removeEventListener is not supported,
 *   useCapture will be ignored.
 * @see http://stackoverflow.com/a/12949687/940217
 * @param {Element} ele
 * @param {string} type
 * @param {function(?)} handler
 * @param {boolean=} useCapture
 * @return {undefined}
 */
Utils.removeEventListener = function (ele, type, handler, useCapture) {
    useCapture = useCapture || false;
    if (ele.removeEventListener) {
        ele.removeEventListener(type, handler, useCapture);
    } else if (ele.detachEvent) {
        ele.detachEvent("on" + type, handler);
    } else {
        ele["on" + type] = null;
    }
};




var isOpenRegex = /(^|\s)Open(\s|$)/;

/** @param {Element} elem */
window['toggleFilterGroup'] = function (elem) {

    var closeFilterGroup = function () {
        setTimeout(function () {
            window.console && window.console.log('closing filter group near element: ', elem);
            Utils.removeEventListener(document.body, 'click', closeFilterGroup, false);
            Utils.removeEventListener(elem, 'click', closeFilterGroup, false);
            elem.getElementsByTagName('ul')[0].setAttribute('aria-expanded', 'false');
         ...