771 votes

Comment mettre l'accent sur le champ de saisie ?

Quelle est la "méthode Angular" pour mettre l'accent sur le champ de saisie dans AngularJS ?

Des exigences plus spécifiques :

  1. Lorsqu'un Modal est ouvert, mettez le focus sur un élément prédéfini <input> à l'intérieur de cette modale.
  2. Chaque fois <input> devient visible (par exemple, en cliquant sur un bouton), mettez le focus dessus.

J'ai essayé de répondre à la première exigence con autofocus mais cela ne fonctionne que lorsque la modale est ouverte pour la première fois, et seulement dans certains navigateurs (par exemple, dans Firefox, cela ne fonctionne pas).

Toute aide sera appréciée.

591voto

Mark Rajcok Points 85912
  1. Lorsqu'une modale est ouverte, mettez le focus sur une <input> prédéfinie à l'intérieur de cette modale.

Définir une directive et lui faire $surveiller une propriété/un déclencheur pour qu'elle sache quand mettre l'élément au point :

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));
            });
        }
    };
}]);

Plunker

Le paramètre $timeout semble être nécessaire pour donner à la modale le temps de s'afficher.

'2.' Chaque fois que <input> devient visible (par exemple, en cliquant sur un bouton), on met le focus dessus.

Créez une directive essentiellement comme celle ci-dessus. Surveillez une propriété scope, et quand elle devient vraie (définissez-la dans votre gestionnaire ng-click), exécutez element[0].focus() . En fonction de votre cas d'utilisation, vous pouvez avoir besoin ou non d'un $timeout pour celui-ci :

<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;
          //});
        }
      });
    }
  };
});

Plunker


Mise à jour 7/2013 : J'ai vu quelques personnes utiliser mes directives originales isolate scope et avoir ensuite des problèmes avec les champs de saisie intégrés (c'est-à-dire un champ de saisie dans la modale). Une directive sans nouvelle portée (ou éventuellement une nouvelle portée enfant) devrait atténuer certains de ces problèmes. J'ai donc mis à jour la réponse ci-dessus pour ne pas utiliser de scopes isolés. Vous trouverez ci-dessous la réponse originale :

Réponse originale pour 1., en utilisant une lunette isolée :

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 .

Réponse originale pour 2., en utilisant une lunette isolée :

<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 .

Puisque nous devons réinitialiser la propriété trigger/focusInput dans la directive, '=' est utilisé pour une liaison de données bidirectionnelle. Dans la première directive, '@' était suffisant. Notez également que lorsque nous utilisons '@', nous comparons la valeur du déclencheur à "true" puisque @ donne toujours une chaîne de caractères.

0 votes

Merci pour votre réponse ! Bien que cela puisse fonctionner dans ce cas précis, je cherche une solution plus générique à ce problème (mise au point). Cela signifie que la directive ne doit pas inclure de noms codés en dur (comme shouldBeOpen ).

0 votes

@Misha, ok, j'ai modifié ma réponse en rendant la directive plus générique, sans noms codés en dur, et avec une portée isolée.

2 votes

Voir aussi la directive "focus" de @Josh : stackoverflow.com/a/14859639/215945 Il n'a pas utilisé d'isolate scope dans son implémentation.

269voto

Ben Lesh Points 39290

(EDIT : J'ai ajouté une solution mise à jour sous cette explication)

Mark Rajcok est l'homme de la situation... et sa réponse est valable, mais elle... a avait un défaut (désolé Mark)...

...Essayez d'utiliser le booléen pour vous concentrer sur l'entrée, puis brouillez l'entrée, puis essayez de l'utiliser à nouveau pour vous concentrer sur l'entrée. Cela ne fonctionnera pas, à moins que vous ne réinitialisiez le booléen à false, puis $digest, puis le réinitialisiez à true. Même si vous utilisez une comparaison de chaînes dans votre expression, vous serez obligé de changer la chaîne en quelque chose d'autre, $digest, puis de la changer à nouveau. (Ce problème a été résolu avec le gestionnaire d'événements de flou).

Je propose donc cette solution alternative :

Utilisez un événement, la fonctionnalité oubliée d'Angular.

JavaScript adore les événements après tout. Les événements sont par nature faiblement couplés, et mieux encore, vous évitez d'ajouter un autre $watch à votre $digest.

app.directive('focusOn', function() {
   return function(scope, elem, attr) {
      scope.$on(attr.focusOn, function(e) {
          elem[0].focus();
      });
   };
});

Donc maintenant vous pouvez l'utiliser comme ceci :

<input type="text" focus-on="newItemAdded" />

et ensuite n'importe où dans votre application...

$scope.addNewItem = function () {
    /* stuff here to add a new item... */

    $scope.$broadcast('newItemAdded');
};

C'est génial parce que vous pouvez faire toutes sortes de choses avec quelque chose comme ça. D'une part, vous pouvez vous connecter à des événements qui existent déjà. D'autre part, vous pouvez faire quelque chose d'intelligent en faisant en sorte que différentes parties de votre application publient des événements auxquels d'autres parties de votre application peuvent s'abonner.

Quoi qu'il en soit, ce type de chose me semble être "événementiel". Je pense qu'en tant que développeurs Angular, nous essayons vraiment d'enfoncer des chevilles en forme de $scope dans des trous en forme d'événement.

Est-ce la meilleure solution ? Je ne sais pas. Elle est a solution.


Solution actualisée

Après le commentaire de @ShimonRachlenko ci-dessous, j'ai légèrement modifié ma méthode de travail. Maintenant, j'utilise une combinaison d'un service et d'une directive qui gère un événement "en coulisse" :

Pour le reste, c'est le même principe que celui décrit ci-dessus.

Voici une démonstration rapide de Plunk

Utilisation

<input type="text" focus-on="focusMe"/>

app.controller('MyCtrl', function($scope, focus) {
    focus('focusMe');
});

Source

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);
    });
  }
});

3 votes

Vous devez envelopper l'appel à $broadcast con $timeout si vous voulez que cela fonctionne lors de la saisie du contrôleur. Sinon, bonne solution.

0 votes

@ShimonRachlenko - Merci. Mais je ne suis pas sûr de ce que vous voulez dire par le $timeout. Si je voulais diffuser lorsque le constructeur du contrôleur était en cours de traitement, je diffuserais juste à ce moment-là. Un délai d'attente ne ferait rien d'autre que de reporter la diffusion à une exécution ultérieure dans la boucle d'événements.

1 votes

Oui, et c'est suffisant pour que la directive s'initialise. De plus, l'événement est diffusé avant que la directive ne commence à l'écouter Encore une fois, ceci n'est nécessaire que lorsque vous voulez déclencher votre directive lorsque vous entrez dans la page.

244voto

ecancil Points 596

J'ai trouvé que certaines des autres réponses étaient trop compliquées alors que tout ce dont vous avez besoin est ceci

app.directive('autoFocus', function($timeout) {
    return {
        restrict: 'AC',
        link: function(_scope, _element) {
            $timeout(function(){
                _element[0].focus();
            }, 0);
        }
    };
});

L'usage est

<input name="theInput" auto-focus>

Nous utilisons le délai d'attente pour laisser les choses dans le domaine s'afficher, même s'il est nul, il attend au moins pour cela - de cette façon, cela fonctionne aussi dans les modales et autres

1 votes

Il y a plusieurs façons de faire cela, une façon possible qui est vraiment directe et facile serait de définir dans le scope (contrôleur ici) l'ID de l'élément sur lequel vous voulez vous concentrer lorsque vous cliquez sur le bouton, puis dans la directive d'écouter simplement ceci. Dans ce cas, vous n'auriez pas besoin de placer la directive à un endroit particulier, juste quelque part dans la page (j'ai utilisé ce genre de directives watcher avec succès dans le passé, en particulier pour la focalisation et le défilement) - Ensuite, si vous utilisez jquery (ce qui simplifierait les choses), il suffit de trouver l'ID de l'élément et de le focaliser.

14 votes

La solution avec un délai de zéro ne fonctionne pas pour moi si l'entrée est située dans une popup modale. Mais même 10 ms règlent le problème

0 votes

@ecancil : J'aime votre approche parce que c'est la plus simple, mais vous devez définir le délai d'attente à ~500ms dans IE parce que l'animation de l'apparence modale entraîne un curseur clignotant en dehors de l'entrée. Je ne connais pas de moyen agréable de faire en sorte que cela se produise lorsque l'animation se termine, alors je le force brutalement avec les 500 ms.

63voto

JordanC Points 2433

Vous pouvez également utiliser la fonctionnalité jqlite intégrée à angular.

angular.element('.selector').trigger('focus');

2 votes

Sans jquery chargé : angular.forEach(document.querySelectorAll('.selector'), function(elem) { elem.focus() ; }) ;

29 votes

N'est-ce pas une mauvaise pratique que de mettre cela dans un contrôleur ?

2 votes

Si mettre cette ligne jqlite à l'intérieur d'un contrôleur est une mauvaise pratique, ne serait-il pas préférable de mettre cette ligne jqlite à l'intérieur d'une directive ?

56voto

onsjjss Points 324

Cela fonctionne bien et c'est une manière angulaire de se concentrer sur le contrôle des entrées.

angular.element('#elementId').focus()

Bien qu'il ne s'agisse pas d'une méthode angulaire pure pour effectuer la tâche, la syntaxe suit le style angulaire. Jquery joue un rôle indirect et accède directement au DOM en utilisant Angular (jQLite => JQuery Light).

Si nécessaire, ce code peut facilement être placé dans une simple directive angulaire où l'élément est directement accessible.

0 votes

Je ne suis pas sûr que ce soit une bonne approche pour les ViewModel-apps. En Angular, cela doit être fait via des directives.

0 votes

Par exemple, si quelqu'un modifie une zone de texte A, vous devez afficher une fenêtre pop-up et mettre l'accent sur une autre zone de texte B.

1 votes

J'obtiens cette erreur lorsque je l'utilise : Looking up elements via selectors is not supported by jqLite!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X