HoffZ
HoffZ

Reputation: 7729

Keyboard navigation in AngularJS table

I'm trying to port a special table/grid form made in Windows (Delphi VCL) to an Angular app.

A simplified version of the angular app can be tested here: jsFiddle demo.

User can add as many rows as he wants, as demonstrated in the jsFiddle.

The question is: Is it possible to solve the following with an Angular directive or some other Angular magic?

  1. jump to next cell (cell to the right or new line) when enter key is pressed.
  2. jump to cell directly beneath on arrow down.
  3. jump to cell directly above on arrow up.

HTML:

<tbody>
<tr ng-repeat="p in persons">
    <td>
        <input ng-model="p.name">
    </td>
    <td>
        <input ng-model="p.age">
    </td>
    <td>
        <button ng-click="add($index)">Add new person</button>
    </td>
</tr>
</tbody>

JS:

function TestingCtrl($scope) {

  $scope.persons = [{
      name: 'Alice',
      age: 20
    }, {
      name: 'Bob',
      age: 30
    }];

  $scope.add = function(index) {
    var newPerson = function() {
      return {
        name: '',
        age: ''
      };
    };
    $scope.persons.splice(index + 1, 0, new newPerson());
  }

}

Upvotes: 1

Views: 8778

Answers (2)

HoffZ
HoffZ

Reputation: 7729

I figured this out. This Plunk will navigate on enter key AND all arrow keys (up, down, left, right). Thanks @Armen for pointing me in the right direction.

<div ng-app="myApp">
<div ng-controller="TestingCtrl">
    <table navigatable>
        <thead>
            <tr>
                <th>Name</th>
                <th>Age</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="p in persons">
                <td>
                    <input type="text" ng-model="p.name">
                </td>
                <td>
                    <input type="text" ng-model="p.age">
                </td>
                <td>
                    <button ng-click="add($index)">Add new person</button>
                </td>
            </tr>
        </tbody>
    </table>
</div>

angular.module("myApp", [])
.controller("TestingCtrl", ["$scope",
  function TestingCtrl($scope) {
    $scope.persons = [{
        name: 'Alice',
        age: 20
      }, {
        name: 'Bob',
        age: 30
      }];

    $scope.add = function(index) {
      var newPerson = function() {
        return {
          name: '',
          age: ''
        };
      };
      $scope.persons.splice(index + 1, 0, new newPerson());
    }

  }
])
.directive('navigatable', function() {
  return function(scope, element, attr) {

    element.on('keypress.mynavigation', 'input[type="text"]', handleNavigation);


    function handleNavigation(e) {

      var arrow = {left: 37, up: 38, right: 39, down: 40};

      // select all on focus
      element.find('input').keydown(function(e) {

        // shortcut for key other than arrow keys
        if ($.inArray(e.which, [arrow.left, arrow.up, arrow.right, arrow.down]) < 0) {
          return;
        }

        var input = e.target;
        var td = $(e.target).closest('td');
        var moveTo = null;

        switch (e.which) {

          case arrow.left:
            {
              if (input.selectionStart == 0) {
                moveTo = td.prev('td:has(input,textarea)');
              }
              break;
            }
          case arrow.right:
            {
              if (input.selectionEnd == input.value.length) {
                moveTo = td.next('td:has(input,textarea)');
              }
              break;
            }

          case arrow.up:
          case arrow.down:
            {

              var tr = td.closest('tr');
              var pos = td[0].cellIndex;

              var moveToRow = null;
              if (e.which == arrow.down) {
                moveToRow = tr.next('tr');
              }
              else if (e.which == arrow.up) {
                moveToRow = tr.prev('tr');
              }

              if (moveToRow.length) {
                moveTo = $(moveToRow[0].cells[pos]);
              }

              break;
            }

        }

        if (moveTo && moveTo.length) {

          e.preventDefault();

          moveTo.find('input,textarea').each(function(i, input) {
            input.focus();
            input.select();
          });

        }

      });


      var key = e.keyCode ? e.keyCode : e.which;
      if (key === 13) {
        var focusedElement = $(e.target);
        var nextElement = focusedElement.parent().next();
        if (nextElement.find('input').length > 0) {
          nextElement.find('input').focus();
        } else {
          nextElement = nextElement.parent().next().find('input').first();
          nextElement.focus();
        }
      }
    }
  };
})

I have done som copy/paste from different sources. Needs refactoring.

Upvotes: 7

Armen
Armen

Reputation: 287

Have a look at this plunk. Pay attention on attribute 'navigatable' added on table. This is not a complete solution of your problem (added only the "enter" button behavior), but just an idea how you can elegantly add navigation functionality to your table inputs. Please note that here is loaded jquery! checkout this doc to better understand jquery usage in angular. Citation from documentation:

jqLite is a tiny, API-compatible subset of jQuery that allows Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most commonly needed functionality with the goal of having a very small footprint.

Upvotes: 2

Related Questions