键盘可访问下拉子菜单的Angular Bootstrap实现?

时间:2015-07-16 19:50:13

标签: angularjs twitter-bootstrap accessibility angular-bootstrap wcag

我的目标是使用以下方法制作具有理智键盘和屏幕阅读器行为的子菜单:

这里是一个起点,显示子菜单(不要)在没有任何自定义的情况下使用键盘:



var myApp = angular.module('myApp', []);

function MyCtrl($scope) {
    $scope.selectItem = function (item) {
        alert(item);
    };
}

/* 
 * No submenu in Bootstrap 3 - need to put CSS in manually.
 * All CSS from https://stackoverflow.com/questions/18023493/bootstrap-3-dropdown-sub-menu-missing 
 */

.dropdown-submenu {
    position: relative;
}
.dropdown-submenu>.dropdown-menu {
    top: 0;
    left: 100%;
    margin-top: -6px;
    margin-left: -1px;
    -webkit-border-radius: 0 6px 6px 6px;
    -moz-border-radius: 0 6px 6px 6px;
    border-radius: 0 6px 6px 6px;
}
.dropdown-submenu:hover > .dropdown-menu {
    display: block;
}
.dropdown-submenu > a:after {
    display: block;
    content: " ";
    float: right;
    width: 0;
    height: 0;
    border-color: transparent;
    border-style: solid;
    border-width:5px 0 5px 5px;
    border-left-color:#cccccc;
    margin-top:5px;
    margin-right:-10px;
}
.dropdown-submenu:hover>a:after {
    border-left-color: #ffffff;
}
.dropdown-submenu.pull-left {
    float: none;
}
.dropdown-submenu.pull-left>.dropdown-menu {
    left: -100%;
    margin-left: 10px;
    -webkit-border-radius: 6px 0 6px 6px;
    -moz-border-radius: 6px 0 6px 6px;
    border-radius: 6px 0 6px 6px;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="MyCtrl">
    <div class="dropdown"> 
        
        <a class="dropdown-toggle btn btn-primary" id="DropdownButton" data-toggle="dropdown" role="button" href="#">DROPDOWN</a>

        <ul class="dropdown-menu" role="menu" aria-labelledby="DropdownButton">
            <li role="presentation"> 
                <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('1')">option 1</a>
            </li>
            <li role="presentation"> 
                <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('2')">option 2</a>
            </li>
            <li role="presentation"> 
                <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('3')">option 3</a>
            </li>
            <li role="presentation" class="dropdown dropdown-submenu" custom-submenu> 
                
                <a class="dropdown-toggle" id="SubmenuButton" role="menuitem" tabindex="0" href="#" aria-haspopup="true">option 4 (has children)</a>

                <ul class="dropdown-menu" role="menu" aria-hidden="true" aria-labelledby="SubmenuButton">
                    <li role="presentation"> 
                        <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('submenu item 1')">
                            child option 1
                        </a>
                    </li>
                    <li role="presentation"> 
                        <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('submenu item 2')">
                            child option 2
                        </a>
                    </li>
                    <li role="presentation"> 
                        <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('submenu item 3')">
                            child option 3
                        </a>
                    </li>
                </ul>
            </li>
            <li role="presentation"> 
                <a role="menuitem" tabindex="0" href="#" ng-click="selectItem('item 5')">option 5</a>
            </li>
        </ul>
    </div>
</div>
&#13;
&#13;
&#13;

Here's a JSFiddle of the above。我会在答案中发布我的解决方案,但我认为它并不完美;我对反馈或替代方案感兴趣。

1 个答案:

答案 0 :(得分:1)

Here's a JSFiddle of my solution(由于某种原因,它不是作为一个片段在这里?)。这是一个名为custom-submenu的自定义指令,我附加到子菜单的最外层(li)。

行为:

  • 向上和向下箭头导航顶级菜单
  • Space打开子菜单,然后你可以向下箭头
  • 从子菜单向上或向下箭头关闭子菜单并返回其父级
  • 输入选择项目

就屏幕阅读器而言,这在FF和IE的NVDA中效果很好,但ChromeVox不喜欢它。

以下是该指令的代码:

myApp.directive("customSubmenu", ['$timeout', function ($timeout) {
    return {
        link: function ($scope, element, attrs) {

            var toggleButton = $(element).find('.dropdown-toggle');
            var submenu = $(element).find('.dropdown-menu');

            /* 
             * handle keydown on the submenu itself - if we arrow up from the first element or down from the last,
             * close submenu
             */
            submenu.keydown(function (event) {
                if (!(event.keyCode === 38 || event.keyCode === 40)) return;

                var links = $(element).find('li:not(.divider):not(.disabled) a');

                if (event.keyCode === 38 && event.target == links[0]) {
                    // first submenu item - up arrow - close submenu, focus toggle button, stop propagation
                    $(element).removeClass('open');
                    submenu.attr('aria-hidden', true);
                    toggleButton.focus();
                    event.stopPropagation();
                    event.preventDefault();
                } else if (event.keyCode === 40 && event.target == links[links.length - 1]) {
                    // last submenu item - down arrow - close submenu, focus toggle button, stop propagation
                    $(element).removeClass('open');
                    submenu.attr('aria-hidden', true);
                    toggleButton.focus();
                    event.stopPropagation();
                    event.preventDefault();
                }
            });

            /* 
             * handle keydown on toggle button - space toggles submenu visibility, arrows navigate outer menu
             */
            toggleButton.keydown(function (event) {
                if (event.keyCode === 32) { // space bar - open/close submenu
                    if ($(element).hasClass('open')) {
                        $(element).removeClass('open');
                        submenu.attr('aria-hidden', true);
                    } else {
                        $(element).addClass('open');
                        submenu.attr('aria-hidden', false);
                    }
                    event.stopPropagation();
                    event.preventDefault();
                } else if (event.keyCode === 40) { // down arrow
                    if (!$(element).hasClass('open')) {
                        // even though the submenu isn't open, the bootstrap dropdown directive will try to focus the 
                        // hidden submenu items, so intercept the keydown and focus the next outer menu item instead
                        var nextSibling = $(element).nextAll('li:not(.divider):not(.disabled):visible');
                        if (nextSibling && nextSibling[0]) {
                            var nextSiblingLink = $(nextSibling[0]).find('a');
                            if (nextSiblingLink && nextSiblingLink[0]) {
                                // focus next menu item
                                $(nextSiblingLink[0]).focus();

                                // while we're at it, let's attach a handler to that next link, telling it to focus this
                                // one when the up arrow is pressed (instead of trying to go into the hidden submenu items)
                                // (TODO: is this going to chain a bunch of these handlers?)
                                $(nextSiblingLink[0]).keydown(function (e) {
                                    if (e.keyCode === 38) { // up
                                        toggleButton.focus();
                                        e.stopPropagation();
                                        e.preventDefault();
                                    };
                                });
                            }
                        }
                        event.stopPropagation();
                        event.preventDefault();
                    }
                }
            });

            /* 
             * handle click on toggle button - open or close submenu 
             */
            toggleButton.click(function (event) {
                if ($(element).hasClass('open')) {
                    $(element).removeClass('open');
                    submenu.attr('aria-hidden', true);
                } else {
                    $(element).addClass('open');
                    submenu.attr('aria-hidden', false);
                }
                event.stopPropagation();
                event.preventDefault();
            });
        }
    }
}]);

我很感激任何建议!