10

Test case: http://jsbin.com/ahugeg/4/edit (Slightly long)

In the above test case, I have three input elements, generated by ng-repeat directive. My intention in this test case, is that hitting the up/down arrows in one of these inputs should move focus to the input in the corresponding direction, if there is an input available in that direction.

I am new to AngularJS, so I might be missing on some straightforward way to do this. Anyway, I defined two new directives (on-up and on-down), to handle the up and down events and am calling the $scope.focusNext and $scope.focusPrev methods, passing the correct entry, relative to which the focus should move. This is where I am stuck.

I know it is not the angular-way to deal with DOM elements in controllers, but I can't see how the focus can be seen as an attribute/property of a model. I even thought of having a separate $scope.focusedEntry, but then should I watch for changes on that property? Even if I do and I detect changes, how can I access the input element corresponding to the entry I want focused?

Any help on how this should be done are very much appreciated.

3 Answers 3

16

I just wrote this up and tested it briefly - it does what you want without all the extra clutter in your controller and in the HTML. See it working here.

HTML:

<body ng-controller="Ctrl">
    <input ng-repeat="entry in entries" value="{{entry}}" key-focus />
</body>

Controller:

function Ctrl($scope) {
  $scope.entries = [ 'apple', 'ball', 'cow' ];
}

Directive:

app.directive('keyFocus', function() {
  return {
    restrict: 'A',
    link: function(scope, elem, attrs) {
      elem.bind('keyup', function (e) {
        // up arrow
        if (e.keyCode == 38) {
          if(!scope.$first) {
            elem[0].previousElementSibling.focus();
          }
        }
        // down arrow
        else if (e.keyCode == 40) {
          if(!scope.$last) {
            elem[0].nextElementSibling.focus();
          }
        }
      });
    }
  };
});
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. Your solution gave me the idea and motivation to work on my own. I took a slightly different approach, defining a directive on the parent of ng-repeated element, that takes the selector of inputs on which focus is to be moved. I'll post my solution in a minute. Thanks again.
6

I had a similar problem and used this simple directive. It works as ng-show and ng-hide would- only with focus, if it's attribute resolves as true:

.directive('focusOn',function() {
    return {
        restrict : 'A', 
        link : function($scope,$element,$attr) {
            $scope.$watch($attr.focusOn,function(focusVal) {
                if(focusVal === true) {
                    setTimeout(function() {
                        $element.focus();
                    },50);
                }
            });
        }
    }
})

2 Comments

Hah! Nice and simple solution.Thank you very much.
Great solution. You need to check focusVal more thoroughly. ng-show and ng-hide use toBoolean(value) which unfortunately is a private angular function. I moved this to a service and call Boolean.toBoolean(focusVal) === true.
4

Inspired by @Trevor's solution, here's what I settled on,

app.directive('focusIter', function () {

    return function (scope, elem, attrs) {
        var atomSelector = attrs.focusIter;

        elem.on('keyup', atomSelector, function (e) {
            var atoms = elem.find(atomSelector),
                toAtom = null;

            for (var i = atoms.length - 1; i >= 0; i--) {
                if (atoms[i] === e.target) {
                    if (e.keyCode === 38) {
                        toAtom = atoms[i - 1];
                    } else if (e.keyCode === 40) {
                        toAtom = atoms[i + 1];
                    }
                    break;
                }
            }

            if (toAtom) toAtom.focus();

        });

        elem.on('keydown', atomSelector, function (e) {
            if (e.keyCode === 38 || e.keyCode === 40)
                e.preventDefault();
        });

    };
});

This defines an attribute focus-iter to be set on the parent element of all the repeated inputs. See this in action here: http://jsbin.com/ahugeg/10/.

The advantage over @Trevor's is that I can set an arbitrary selector for the value of focus-iter attribute to specify exactly which elements the focus jumping should work with. As a crazy example, try setting focus-iter attribute to input:even :). This helps since in my application, the inputs come with quite a bit of extra markup around them, unlike the test case.

3 Comments

That's actually pretty neat what you did there. I like how basically anything works such as focus-iter="form>*".
Thanks. Or even focus-iter="[key-focus]", when the inputs have key-focus attribute like the markup in your answer :)
can help me with input inside table cell ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.