Reputation: 18127
The question stems from an ng-grid interaction, but I've stubbed my toe on it a few other places.
An interaction with the page raises the need for the focus to be on a certain control.
For ng-grid this might be clicking on the filter button in the header. This causes a popup (but not really a modal dialog) input control to appear which then needs the focus, so the user doesn't then have to click a second time to enter the filter text. I'm assuming this should be done in the directive, but how do you get that directive at the point this is happening?
Another interaction might be after an attempted save on a form. Let say there is a validation that can't happen in the client, (multi-user app, race condition to acquire a resource). When the error returns from the promise, I'd like to put the cursor in the field that needs to be changed. )Which field depends on the error response from the server.)
I think what I'm really looking for is the equivalent of an $('#id').focus() that accepts an ng-model and finds the correct control on the page and puts the cursor in that field, but one that could be used at the completion of promise or in reaction to an event. I realize that linkage from model => DOM is problematic (a model could appear many places on the page), but in the case of form input, that probably isn't true, or could be made not-true to facilitate this kind of application response.
I'm a little lost as to where the logic should be and how I could get hold of the object that would contain it.
Upvotes: 1
Views: 3555
Reputation: 793
There's a similar question that has some good answers. I think the second answer comes closest to what you're asking for
How to set focus on input field?
Instead of binding a boolean "should be focused" field, the directive uses events to set focus on the desired element.
There are several advantages to this approach over the $('#id').focus() functionality you were describing. The biggest one is that with the event approach, your controller can be mocked and tested outside the DOM, because the tests can just look for fired events, rather than checking which DOM element has focus.
Here's a really generic example of how you might do it for field validation. Kudos to @blesh for the directive/service implementations
In your form you can add the focus-on
directive. Just use a unique event name for each field:
<input type="text" name="name" focus-on="input-name" />
<input type="text" name="email" focus-on="input-email" />
In your controller, after you do field validation, you can call the "focus" service on the using the focus-on="input-{name}"
value you specified in your template. Here I just prepend input-
to the field name, but you could use any naming convention you like.
app.controller('MyCtrl', function($scope, focus) {
$scope.validate = function() {
// ...
// Do your field validation
// ...
if (invalidFields.length) {
focus('input-' + invalidFields[0].name);
}
};
});
Then just define your directive and service
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on('focusOn', function(e, name) {
if(name === attr.focusOn) {
elem[0].focus();
}
});
};
});
app.factory('focus', function ($rootScope, $timeout) {
return function(name) {
$timeout(function (){
$rootScope.$broadcast('focusOn', name);
});
}
});
Upvotes: 1