12

Consider the following markup -

<ul id="list">
    <li class="list-item" tabindex="0">test 1</li>
    <li class="list-item" tabindex="1">test 2</li>
    <li class="list-item" tabindex="2">test 3</li>
    <li class="list-item" tabindex="3">test 4</li>
    <li class="list-item" tabindex="4">test 5</li>
    <li class="list-item" tabindex="5">test 6</li>
    <li class="list-item" tabindex="6">test 7</li>
</ul>

and this piece of jQuery code -

$(".list-item").bind({
    keydown: function(e) {
        var key = e.keyCode;
        var target = $(e.currentTarget);

        switch(key) {
            case 38: // arrow up
                target.prev().focus();
                break;
            case 40: // arrow down
                target.next().focus();
                break;
        }
    },

    focusin: function(e) {
        $(e.currentTarget).addClass("selected");
    },

    focusout: function(e) {
        $(e.currentTarget).removeClass("selected");
    }
});
$("li").first().focus();

How do I port this code in angular? I have this so far -

 <li class="list-item" ng-repeat="item in items" tabindex="{{item.tabIndex}}">
                        {{item.name}}
                    </li>

How do I do the binding in angular?

tempid
  • 7,838
  • 28
  • 71
  • 101
  • Thanks. I'm already looking at them. I'm new to angular so wrapping my head around directives is challenging. – tempid May 31 '13 at 15:03
  • [The answer in this thread](http://stackoverflow.com/questions/15044494/what-is-angularjs-way-to-create-global-keyboard-shortcuts) uses the above method to capture key-events. – rGil May 31 '13 at 17:21
  • possible duplicate of [Navigate the UI using only keyboard](http://stackoverflow.com/questions/17388021/navigate-the-ui-using-only-keyboard) – Kevin Jan 05 '15 at 19:39

4 Answers4

11

The best way I think is to use the tabindex to locate elements for focus. Try something like this;

<li class="list-item" ng-repeat="item in items" 
    tabindex="{{item.tabIndex}}"
    ng-class="item.selected"
    ng-keydown="onKeydown(item, $event)" ng-focus="onFocus(item)">{{item.name}}
</li>

Then in your controller you want the Keydown handler;

var KeyCodes = {
    BACKSPACE : 8,
    TABKEY : 9,
    RETURNKEY : 13,
    ESCAPE : 27,
    SPACEBAR : 32,
    LEFTARROW : 37,
    UPARROW : 38,
    RIGHTARROW : 39,
    DOWNARROW : 40,
};

$scope.onKeydown = function(item, $event) {
        var e = $event;
        var $target = $(e.target);
        var nextTab;
        switch (e.keyCode) {
            case KeyCodes.ESCAPE:
                $target.blur();
                break;
            case KeyCodes.UPARROW:
                nextTab = - 1;
                break;
            case KeyCodes.RETURNKEY: e.preventDefault();
            case KeyCodes.DOWNARROW:
                nextTab = 1;
                break;
        }
        if (nextTab != undefined) {
            // do this outside the current $digest cycle
            // focus the next element by tabindex
           $timeout(() => $('[tabindex=' + (parseInt($target.attr("tabindex")) + nextTab) + ']').focus());
        }
};

And a keep-it-simple focus handler;

$scope.onFocus = function(item, $event) {
    // clear all other items
    angular.forEach(items, function(item) {
        item.selected = undefined;
    });

    // select this one
    item.selected = "selected";
};

That's off the top of my head, in case it helps anyone coming across this thread like I did.

cirrus
  • 5,624
  • 8
  • 44
  • 62
9

You can also create an angular directive like the one below:

claimApp.directive('keyNavigation', function ($timeout) {
    return function (scope, element, attrs) {
        element.bind("keydown keypress", function (event) {
            if (event.which === 38) {
                var target = $(event.target).prev();
                $(target).trigger('focus');
            }
            if (event.which === 40) {
                var target = $(event.target).next();
                $(target).trigger('focus');
            }
        });
    };
});

And then use it in your HTML like this:

<li class="list-item" ng-repeat="item in items" key-navigation>
                    {{item.name}}
                </li>
aholtry
  • 2,723
  • 3
  • 24
  • 26
4

This question was answered a few months before it was asked and has a live working example at Plunker.

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

app.controller('testCtrl',function ($scope, $window) {
$scope.console = $window.console;

$scope.records = [];
for (var i = 1; i <= 9; i++) {
$scope.records.push({ id: i, navIndex: i, name: 'record ' + i});
}

$scope.focusIndex = 3;

$scope.open = function ( index ) {
var record = $scope.shownRecords[ index ]
console.log('opening : ', record );
 };

$scope.keys = [];
$scope.keys.push({ code: 13, action: function() { $scope.open( $scope.focusIndex ); }});
$scope.keys.push({ code: 38, action: function() { $scope.focusIndex--; }});
$scope.keys.push({ code: 40, action: function() { $scope.focusIndex++; }});

And the link to the similar question asked a few weeks before this thread is Stackoverflow

Community
  • 1
  • 1
b k
  • 199
  • 1
  • 10
1

Directive without jQuery

.directive('arrow', function ($timeout) {
    return function (scope, element, attrs) {
        element.bind("keydown keypress", function (event) {
            if (event.which === 38) {
              var prevNode = element[0].previousElementSibling;
              var prev = angular.element(prevNode);
              prev[0].focus();
            }
            if (event.which === 40) {
                var target = element.next();
                target[0].focus();
            }
        });
    };
});

Usages in html

<tr arrow>
  <td>test 1</td>                       
</tr>
<tr arrow>
  <td>test 2</td>                       
</tr>
Bheru Lal Lohar
  • 880
  • 9
  • 17