1

So I have a variable number of input fields and after saving them I'd like to return focus to the first of them.

HTML Code:

<td ng-repeat='col in list.cols'>
  <input class='colData' type='text' ng-model='newentry[$index]'  on-press-enter="addRow(newentry)">
</td>

As you can probably read I call 'addRow()' when pressing enter and then add the values to my data model. This works fine.

Now after adding the data I would like to set the focus to the first of these input fields.
I am trying to resist the urge to fall back on my jQuery knowledge. If i included jQuery I could just do $('input.colData').first().focus();.

But I'm really trying to learn to do things 'the angular way'.

How would I approach this scenario?
Is there a way maybe to select an input field by the model it contains?

thanks,
J

UPDATE: I don't think this is a duplicate, as in my case I can't add a directive to the input field, as they are created dynamically by angular and only the first one should be focussed. Their number is, as stated above, variable. I fail to see how to identify the first among them aside from the jQuery one-liner.

Jan Paepke
  • 1,997
  • 15
  • 25
  • See duplicate question/answer: http://stackoverflow.com/questions/14833326/how-to-set-focus-in-angularjs – MarcoS Jul 07 '14 at 14:07
  • Hi. This is actually not a duplicate, I even read the other answer before. In my case I can't add a directive, because I add multiple input fields using ng-repeat and only the first should be focussed. – Jan Paepke Jul 07 '14 at 14:24

2 Answers2

2

Use Case

How to focus the first element using a directive after a submission of a form? The following assumptions were made:

  • Form submission is done through Ajax either through $resource or $service
  • IE 9 support is required
  • "Angular-Way" is required

For a demonstration, please see this jsFiddle. Let me know if I have understood your problem properly.

Controller

The controller will contain the code for managing your data or submitting your resource to the server. It will then broadcast an event that data has been submitted. Since no code was provided in the original post, I've taken the liberty of filling in the gaps.

Alternatively, a $promise could be used to trigger the event which would provide a means to handle any server issues.

function ctrl($scope) {
    $scope.items = [ 'thing 1', 'thing 2', 'thing 3' ];
    $scope.data = [];

    $scope.addRow = function(newEntry) {
        var entry = newEntry || 'thing ' + $scope.items.length + 1;
        $scope.$evalAsync(function() {
            $scope.items.push(entry);
        });
    };

    $scope.submit = function() {
        console.log("Simulating a submit");
        $scope.$broadcast("formSubmitted");
    };
}

Directive

The directive will respond to the event by focusing the first element. The post-link is used to initialize the events and setup the listeners for the event. When the event is triggered, the element will be focused. This creates a clear separation of data and presentation.

function onPressEnter() {
    return {
        restrict : 'A',
        scope: {
            onPressEnter: '&'
        },
        link: function($scope, $element, $attr, $ctrl) {
            $element.on('keyup', function(e) {
                if (e.which === 13) {
                    $scope.onPressEnter();
                }
            });

            $scope.$on("formSubmitted", function() {
                if ($scope.$parent.$first) {
                    $element[0].focus();
                }
            });
        }
    }
};

Directive #2

Alternatively, you can isolate this directive as a reusable component and use the following. All you would have to do is add on-submit-focus="0" to any element to have it focus on its first child. on-submit-focus="1" would select the second child, and so on... An updated demonstration is available here.

function onSubmitFocus() {
    return function($scope, $element, $attr) {
        $scope.$on('formSubmitted', function() {
            if ($scope.$index === +$attr.onSubmitFocus) {
                $element.children()[0].focus();
            }
        });
    };
}
Pete
  • 3,246
  • 4
  • 24
  • 43
  • Thank you for taking the time for this very extensive and precise answer. It even helped me to understand angular logic a little better. I actually ended up solving this issue using your first comment and an additional module: https://github.com/goodeggs/ng-focus-on I have added focus-on="{{$first ? 'newRowFirstField' : ''}}") to the input field and call focus("newRowFirstField"); at the end of my addRow method. I guess I have to get used to things being a little more verbose in angular. I'm trying really hard to do things right and to avoid known paths (jQuery). Big thumbs up for your effort! – Jan Paepke Jul 07 '14 at 15:52
  • You're welcome. We're all here to learn. I can't recommend this enough, but read this [post](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background). It helped me so much when I was staring out Angular. I ran into a lot of jQuery roadblocks as well, but now I feel more comfortable stepping away from jQuery. – Pete Jul 07 '14 at 15:54
  • I actually read it before and +1ed it. But it was at the very beginning. I'll read it again now and see if I understand it better. :) thanks again... – Jan Paepke Jul 07 '14 at 15:57
1

You can check for the first element and apply the attribute "autofocus" in that case

<td ng-repeat='col in list.cols'>
  <input class='colData' type='text' ng-model='newentry[$index]'  on-press-enter="addRow(newentry)" ng-if="$first" autofocus>
  <input class='colData' type='text' ng-model='newentry[$index]'  on-press-enter="addRow(newentry)" ng-if="!$first">
</td>
cardeol
  • 2,218
  • 17
  • 25
  • I would use `autofocus="{{ $first ? 'autofocus' : '' }}"` as the `ng-if="$first"` will only print out the first input and not render the others. – Pete Jul 07 '14 at 14:51
  • I think it will create the attribute autofocus with an empty value – cardeol Jul 07 '14 at 14:56
  • I was thinking about using ng-attr-autofocus, but no time to test yet – cardeol Jul 07 '14 at 14:57
  • 1
    Using the expression would keep your template DRY as you won't need to repeat the second line for `!$first`. – Pete Jul 07 '14 at 14:59
  • So the way to go is add a directive to all of them, then find out wich has the attribute set to a certain value ('autofocus' in this case') within a directive that waits for a model to change. Then in the controller change this model (or call an event) that will trigger the element to be focussed? This does seem very complicated and verbose for a seemingly simple task. I thought angular was supposed to make code more manageable. Very daunting to be honest. :( I might end up doing it the frowned upon jQuery way... – Jan Paepke Jul 07 '14 at 15:04