仅使用键盘导航UI

tem*_*pid 9 navigation keypress angularjs angular-ui

我正在尝试仅使用键盘浏览记录列表.当页面加载时,默认的"焦点"应该在第一条记录上,当用户单击键盘上的向下箭头时,需要关注下一条记录.当用户单击向上箭头时,应该关注先前的记录.当用户单击Enter按钮时,它应该将它们带到该记录的详细信息页面.

这是我到目前为止在Plunkr上所拥有的.

似乎AngularJS在1.1.5(不稳定)中支持这一点,我们不能在生产中使用它.我目前正在使用1.0.7.我希望做这样的事情 - 密钥应该在文档级别处理.当用户按下某个键时,代码应该在允许的键数组中查找.如果找到匹配(例如向下键代码),它应该移动焦点(应用.highlight css)到下一个元素.当按下enter时,它应该抓取.highlight css的记录并获取记录ID以供进一步处理.

谢谢!

Tos*_*osh 14

以下是您可以选择的示例:http: //plnkr.co/edit/XRGPYCk6auOxmylMe0Uu?p = preview

<body key-trap>
  <div ng-controller="testCtrl">
    <li ng-repeat="record in records">
      <div class="record"
           ng-class="{'record-highlight': record.navIndex == focu sIndex}">
        {{ record.name }}
      </div>
    </li>
  </div>
</body>
Run Code Online (Sandbox Code Playgroud)

这是我能想到的最简单的方法.它将指令绑定keyTrap到将事件和 消息body捕获到子范围的指令.元素持有者范围将捕获消息并简单地增加或减少focusIndex或在命中时触发函数.keydown$broadcastopenenter

编辑

http://plnkr.co/edit/rwUDTtkQkaQ0dkIFflcy?p=preview

现在支持,有序/过滤列表.

事件处理部分没有改变,但现在使用$index并且还结合了过滤列表缓存技术来跟踪哪个项目正在集中.

  • 感谢您的反馈.使用更加困难的用例来挑战它总是充满乐趣和乐趣.我添加了支持排序/筛选列表的附加代码. (2认同)

use*_*361 5

到目前为止提供的所有解决方案都有一个共同的问题.指令不可重用,它们需要知道在控制器提供的父$ scope中创建的变量.这意味着如果你想在不同的视图中使用相同的指令,你需要重新实现你以前的控制器所做的一切,并确保你使用相同的变量名,因为指令基本上有硬编码的$ scope变量名在他们中.您肯定无法在同一父作用域内两次使用相同的指令.

解决这个问题的方法是在指令中使用隔离范围.通过执行此操作,您可以通过一般地参数化父作用域所需的项来使指令可重用,而不管父$ scope.

在我的解决方案中,控制器需要做的唯一事情是提供一个selectedIndex变量,该变量指令用于跟踪当前选择的表中的哪一行.我可以将此变量的责任隔离到指令,但是通过使控制器提供变量,它允许您操作指令外的表中当前选定的行.例如,您可以在控制器中实现"单击选择行",同时仍然使用箭头键在指令中进行导航.

指令:

angular
    .module('myApp')
    .directive('cdArrowTable', cdArrowTable);
    .directive('cdArrowRow', cdArrowRow);

function cdArrowTable() {
    return {
        restrict:'A',
        scope: {
            collection: '=cdArrowTable',
            selectedIndex: '=selectedIndex',
            onEnter: '&onEnter'
        },
        link: function(scope, element, attrs, ctrl) {
            // Ensure the selectedIndex doesn't fall outside the collection
            scope.$watch('collection.length', function(newValue, oldValue) {
                if (scope.selectedIndex > newValue - 1) {
                    scope.selectedIndex = newValue - 1;
                } else if (oldValue <= 0) {
                    scope.selectedIndex = 0;
                }
            });

            element.bind('keydown', function(e) {
                if (e.keyCode == 38) {  // Up Arrow
                    if (scope.selectedIndex == 0) {
                        return;
                    }
                    scope.selectedIndex--;
                    e.preventDefault();
                } else if (e.keyCode == 40) {  // Down Arrow
                    if (scope.selectedIndex == scope.collection.length - 1) {
                        return;
                    }
                    scope.selectedIndex++;
                    e.preventDefault();
                } else if (e.keyCode == 13) {  // Enter
                    if (scope.selectedIndex >= 0) {
                        scope.collection[scope.selectedIndex].wasHit = true;
                        scope.onEnter({row: scope.collection[scope.selectedIndex]});
                    }
                    e.preventDefault();
                }

                scope.$apply();
            });
        }
    };
}

function cdArrowRow($timeout) {
    return {
        restrict: 'A',
        scope: {
            row: '=cdArrowRow',
            selectedIndex: '=selectedIndex',
            rowIndex: '=rowIndex',
            selectedClass: '=selectedClass',
            enterClass: '=enterClass',
            enterDuration: '=enterDuration'  // milliseconds
        },
        link: function(scope, element, attrs, ctr) {
            // Apply provided CSS class to row for provided duration
            scope.$watch('row.wasHit', function(newValue) {
                if (newValue === true) {
                    element.addClass(scope.enterClass);
                    $timeout(function() { scope.row.wasHit = false;}, scope.enterDuration);
                } else {
                    element.removeClass(scope.enterClass);
                }
            });

            // Apply/remove provided CSS class to the row if it is the selected row.
            scope.$watch('selectedIndex', function(newValue, oldValue) {
                if (newValue === scope.rowIndex) {
                    element.addClass(scope.selectedClass);
                } else if (oldValue === scope.rowIndex) {
                    element.removeClass(scope.selectedClass);
                }
            });

            // Handles applying/removing selected CSS class when the collection data is filtered.
            scope.$watch('rowIndex', function(newValue, oldValue) {
                if (newValue === scope.selectedIndex) {
                    element.addClass(scope.selectedClass);
                } else if (oldValue === scope.selectedIndex) {
                    element.removeClass(scope.selectedClass);
                }
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该指令不仅允许您使用箭头键导航表,还允许您将回调方法绑定到Enter键.因此,当按下回车键时,当前选择的行将作为参数包含在使用指令(onEnter)注册的回调方法中.

作为一个额外的奖励,您还可以将CSS类和持续时间传递给cdArrowRow指令,以便当在所选行上点击回车键时,传入的CSS类将应用于行元素,然后在传递后删除持续时间(以毫秒为单位).这基本上允许您执行某些操作,例如在按下回车键时使行闪烁不同颜色.

查看用法:

<table cd-arrow-table="displayedCollection"
       selected-index="selectedIndex"
       on-enter="addToDB(row)">
    <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="row in displayedCollection" 
            cd-arrow-row="row" 
            selected-index="selectedIndex" 
            row-index="$index" 
            selected-class="'mySelcetedClass'" 
            enter-class="'myEnterClass'" 
            enter-duration="150"
        >
            <td>{{row.firstName}}</td>
            <td>{{row.lastName}}</td>
        </tr>
    </tbody>
</table>
Run Code Online (Sandbox Code Playgroud)

控制器:

angular
    .module('myApp')
    .controller('MyController', myController);

    function myController($scope) {
        $scope.selectedIndex = 0;
        $scope.displayedCollection = [
            {firstName:"John", lastName: "Smith"},
            {firstName:"Jane", lastName: "Doe"}
        ];
        $scope.addToDB;

        function addToDB(item) {
            // Do stuff with the row data
        }
    }
Run Code Online (Sandbox Code Playgroud)