¿Cómo poner el foco en el campo de entrada?
¿Cuál es la 'forma angular' de establecer el foco en el campo de entrada en AngularJS?
Requisitos más específicos:
- Cuando se abre un modal , establezca el foco en un predefinido
<input>
dentro de este modal. - Cada vez
<input>
que sea visible (por ejemplo, al hacer clic en algún botón), establezca el foco en él.
Intenté cumplir el primer requisito con autofocus
, pero esto funciona sólo cuando se abre Modal por primera vez, y sólo en ciertos navegadores (por ejemplo, en Firefox no funciona).
- Cuando se abre un modal, establezca el foco en una <entrada> predefinida dentro de este modal.
Defina una directiva y haga que $watch una propiedad/disparador para que sepa cuándo enfocar el elemento:
Name: <input type="text" focus-me="shouldBeOpen">
app.directive('focusMe', ['$timeout', '$parse', function ($timeout, $parse) {
return {
//scope: true, // optionally create a child scope
link: function (scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function (value) {
console.log('value=', value);
if (value === true) {
$timeout(function () {
element[0].focus();
});
}
});
// to address @blesh's comment, set attribute value to 'false'
// on blur event:
element.bind('blur', function () {
console.log('blur');
scope.$apply(model.assign(scope, false));
});
}
};
}]);
plomero
El $timeout parece ser necesario para darle tiempo al modal para renderizarse.
'2.' Cada vez que <input> se vuelve visible (por ejemplo, al hacer clic en algún botón), establezca el foco en él.
Cree una directiva esencialmente como la anterior. Observe alguna propiedad de alcance y, cuando se convierta en verdadera (configúrela en su controlador ng-click), ejecútela element[0].focus()
. Dependiendo de su caso de uso, es posible que necesite o no un $timeout para este:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" ng-model="myInput" focus-me="focusInput"> {{ myInput }}
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$watch(attrs.focusMe, function(value) {
if(value === true) {
console.log('value=',value);
//$timeout(function() {
element[0].focus();
scope[attrs.focusMe] = false;
//});
}
});
}
};
});
plomero
Actualización 7/2013 : He visto a algunas personas usar mis directivas de alcance aislado originales y luego tener problemas con los campos de entrada incrustados (es decir, un campo de entrada en el modal). Una directiva sin un nuevo alcance (o posiblemente un nuevo alcance secundario) debería aliviar algo del dolor. Entonces, arriba actualicé la respuesta para no usar ámbitos aislados. A continuación se muestra la respuesta original:
Respuesta original para 1., utilizando un alcance aislado:
Name: <input type="text" focus-me="{{shouldBeOpen}}">
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '@focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === "true") {
$timeout(function() {
element[0].focus();
});
}
});
}
};
});
Plunker .
Respuesta original para 2., utilizando un alcance aislado:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" focus-me="focusInput">
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '=focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === true) {
//console.log('trigger',value);
//$timeout(function() {
element[0].focus();
scope.trigger = false;
//});
}
});
}
};
});
Plunker .
Dado que necesitamos restablecer la propiedad trigger/focusInput en la directiva, '=' se usa para el enlace de datos bidireccional. En la primera directiva, '@' era suficiente. También tenga en cuenta que cuando usamos '@' comparamos el valor de activación con "verdadero", ya que @ siempre da como resultado una cadena.
##(EDITAR: agregué una solución actualizada debajo de esta explicación)
Mark Rajcok es el hombre... y su respuesta es una respuesta válida, pero ha tenido un defecto (lo siento Mark)...
... Intente usar el valor booleano para enfocarse en la entrada, luego difumine la entrada y luego intente usarlo para enfocar la entrada nuevamente. No funcionará a menos que restablezca el valor booleano a falso, luego $digest y luego lo restablezca a verdadero. Incluso si usa una comparación de cadenas en su expresión, se verá obligado a cambiar la cadena por otra cosa, $digest, y luego volver a cambiarla. (Esto se ha solucionado con el controlador de eventos de desenfoque).
Entonces propongo esta solución alternativa:
Utilice un evento, la característica olvidada de Angular.
Después de todo, a JavaScript le encantan los eventos. Los eventos están inherentemente débilmente acoplados y, mejor aún, evitas agregar otro $watch a tu $digest.
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on(attr.focusOn, function(e) {
elem[0].focus();
});
};
});
Entonces ahora podrías usarlo así:
<input type="text" focus-on="newItemAdded" />
y luego en cualquier lugar de tu aplicación...
$scope.addNewItem = function () {
/* stuff here to add a new item... */
$scope.$broadcast('newItemAdded');
};
Esto es fantástico porque puedes hacer todo tipo de cosas con algo como esto. Por un lado, podría vincularse a eventos que ya existen. Por otra parte, empiezas a hacer algo inteligente al hacer que diferentes partes de tu aplicación publiquen eventos a los que otras partes de tu aplicación pueden suscribirse.
De todos modos, este tipo de cosas me grita "impulsado por eventos". Creo que, como desarrolladores de Angular, nos esforzamos mucho en introducir clavijas con forma de $scope en agujeros con forma de evento.
¿Es la mejor solución? No sé. Es una solución.
Solución actualizada
Después del comentario de @ShimonRachlenko a continuación, cambié ligeramente mi método para hacer esto. Ahora uso una combinación de un servicio y una directiva que maneja un evento "detrás de escena":
Aparte de eso, es el mismo principio descrito anteriormente.
Aquí hay una demostración rápida de Plunk.
###Uso
<input type="text" focus-on="focusMe"/>
app.controller('MyCtrl', function($scope, focus) {
focus('focusMe');
});
###Fuente
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);
});
}
});
He descubierto que algunas de las otras respuestas son demasiado complicadas cuando todo lo que realmente necesitas es esto.
app.directive('autoFocus', function($timeout) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$timeout(function(){
_element[0].focus();
}, 0);
}
};
});
el uso es
<input name="theInput" auto-focus>
Usamos el tiempo de espera para permitir que las cosas en el dom se procesen, aunque sea cero, al menos espera eso; de esa manera esto funciona en modales y demás también.
HTML tiene un atributo autofocus
.
<input type="text" name="fname" autofocus>
http://www.w3schools.com/tags/att_input_autofocus.asp
También puede utilizar la funcionalidad jqlite integrada en angular.
angular.element('.selector').trigger('focus');