Mettre l'accent sur la première entrée invalide dans le formulaire AngularJs

j'ai lu plusieurs articles et questions StackOverflow relatifs à la mise au point dans AngularJs.

malheureusement, tous les exemples que j'ai lus supposent qu'il y a un attribut que je peux ajouter à l'élément pour obtenir la mise au point, par exemple une directive .

Cependant que faire si je ne sais pas à l'avance de l'entrée pour définir le focus? En particulier, comment puis-je mettre l'accent sur le premier élément d'entrée dans une forme qui a défini $invalides-c'est-à-dire un élément qui échoue la validation. Il pourrait y avoir plusieurs entrées qui échouent à la validation, donc je ne peux pas utiliser une directive qui essaie juste d'appeler .focus() sur cette base. (Je fais ceci pour des raisons D'accessibilité/WCAG, sa bonne pratique de le faire sur soumettre étant cliqué pour minimiser les touches pour trouver le premier champ qui a échoué la validation).

l'objet $ error donnera toutes les commandes qui échouent la validation, mais elles sont groupées par le type de défaillance pas dans l'ordre d'apparition sur le formulaire.

je suis sûr que je peux trouver une façon de faire ça. Une directive sur le formulaire, qui reçoit une diffusion lorsque focus doit être défini - cette directive peut alors rechercher le premier élément $invalide. Cependant, cela semble très complexe et j'aimerais savoir s'il s'agit d'une meilleure façon de le faire.

57
demandé sur Community 2013-12-04 05:28:21

12 réponses

Ok, donc la réponse était plus simple que je ne le pensais.

j'avais besoin d'une directive pour mettre sur le formulaire lui-même, avec un gestionnaire d'événements à la recherche de l'événement submit. Cela peut alors traverser le DOM à la recherche du premier élément qui a le .ng-classe invalide dessus.

exemple utilisant jQLite:

myApp.directive('accessibleForm', function () {
    return {
        restrict: 'A',
        link: function (scope, elem) {

            // set up event handler on the form element
            elem.on('submit', function () {

                // find the first invalid element
                var firstInvalid = elem[0].querySelector('.ng-invalid');

                // if we find one, set focus
                if (firstInvalid) {
                    firstInvalid.focus();
                }
            });
        }
    };
});

l'exemple utilise ici une directive D'attribut, vous pouvez étendre l'exemple pour que ce soit une directive d'élément (restreindre: 'E') et inclure un modèle qui convertit ceci en a. C'est cependant une question de préférence personnelle.

85
répondu iandotkelly 2015-03-31 17:03:14

vous pouvez créer une directive comme d'autres réponses ou bien vous pouvez l'accrocher avec ng-submit et implémenter la logique dans le contrôleur.

:

<form name='yourForm' novalidate ng-submit="save(yourForm)">
</form>

contrôleur:

$scope.save = function(yourForm) {
  if (!yourForm.$valid) {
    angular.element("[name='" + yourForm.$name + "']").find('.ng-invalid:visible:first').focus();
    return false;
  }
};
14
répondu nnattawat 2015-11-01 01:48:15

Vous pouvez également utiliser angulaire.élément

angular.element('input.ng-invalid').first().focus();

Vue

<form name="myForm" novalidate="novalidate" data-ng-submit="myAction(myForm.$valid)" autocomplete="off"></form>

contrôleur

$scope.myAction= function(isValid) {
    if (isValid) {
        //You can place your ajax call/http request here
    } else {
        angular.element('input.ng-invalid').first().focus();
    }
};

utilisé ngMessages pour la validation

Le pas de jquery façon

angular.element($document[0].querySelector('input.ng-invalid')).focus();

lors de l'utilisation de cette méthode, besoin de passer $document comme paramètre dans votre contrôleur angulaire

angular.module('myModule')
.controller('myController', ['$document', '$scope', function($document, $scope){
    // Code Here
}]);
11
répondu Sajan Mullappally 2016-03-17 05:46:09

vous pouvez utiliser pur jQuery pour sélectionner la première entrée invalide:

$('input.ng-invalid').first().focus();

6
répondu Edmond Chui 2015-04-02 05:28:46

    .directive('accessibleForm', function () {
        return {
            restrict: 'A',
            link: function (scope, elem) {
                // set up event handler on the form element
                elem.on('submit', function () {
                    // find the first invalid element
                    var firstInvalid = elem[0].querySelector('.ng-invalid');
                    if (firstInvalid && firstInvalid.tagName.toLowerCase() === 'ng-form') {
                        firstInvalid = firstInvalid.querySelector('.ng-invalid');
                    }
                    // if we find one, set focus
                    if (firstInvalid) {
                        firstInvalid.focus();
                    }
                });
            }
        };
    })
4
répondu chaojidan 2015-10-23 12:00:37

j'ai joué avec cette idée pendant un certain temps et j'ai trouvé ma propre solution, elle peut aider les gens qui sont adverses à ramper le DOM, comme moi.

autant que je puisse dire, les éléments de formulaire s'enregistrent eux-mêmes dans un ordre cohérent (c.-à-d. de haut en bas) et leurs noms et états de validation sont disponibles sur le scope grâce à ce que jamais le nom du formulaire est (par exemple $scope.myForm).

Cela me conduisent à penser qu'il y avait un moyen de trouver le premier entrée de forme invalide sans ramper le DOM et à la place ramper les structures internes de JS angulaire. Ci-dessous est ma solution, mais il suppose que vous avez une autre façon de se concentrer sur les éléments de forme, je diffuse à une directive personnalisée, si la diffusion correspond au nom de l'élément qu'il se concentrera (ce qui est utile en soi que vous obtenez pour contrôler quel élément prend la concentration sur la première charge).

la fonction pour trouver le premier invalide (idéalement partagé à la contrôleurs par le biais d'un service)

function findFirstInvalid(form){
    for(var key in form){
        if(key.indexOf("$") !== 0){
            if(form[key].$invalid){
                return key;
            }
        }
    }
}

et la directive custom focus

directives.directive('focus', function($timeout){
    return {
        require: 'ngModel',
        restrict: 'A',
        link: function(scope, elem, attrs, ctrl){
            scope.$on('inputFocus', function(e, name){
                if(attrs.name === name){
                    elem.focus();
                }
            });
        }
    }
});
2
répondu h.coates 2014-02-20 04:59:41

j'ai fait quelques petites modifications à la grande solution écrite par iandotkelly. Cette solution ajoute une animation qui se déclenche sur le défilement, et fait un focus sur l'élément sélectionné après.

myApp.directive('accessibleForm', function () {
    return {
        restrict: 'A',
        link: function (scope, elem) {

            // set up event handler on the form element
            elem.on('submit', function () {

                // find the first invalid element
                var firstInvalid = elem[0].querySelector('.ng-invalid');

                // if we find one, we scroll with animation and then we set focus
                if (firstInvalid) {
                     angular.element('html:not(:animated),body:not(:animated)')
                    .animate({ scrollTop: angular.element(firstInvalid).parent().offset().top },
                        350,
                        'easeOutCubic',
                        function () {
                            firstInvalid.focus();
                        });
                }
            });
        }
    };
});
1
répondu Mathemagician 2016-10-26 08:45:34

une seule ligne:

if($scope.formName.$valid){
    //submit
}
else{
    $scope.formName.$error.required[0].$$element.focus();
}
1
répondu sonphuong 2017-10-27 07:50:40

, Vous pouvez ajouter un attribut dans chaque élément de formulaire qui est une fonction (idéalement une directive) qui reçoit un id de champ. Cet id de champ devrait être corrélé d'une façon ou d'une autre à votre objet $error. La fonction peut vérifier si l'id est dans votre objet $error, et si oui, retourner le paramètre attribut pour une erreur.

<input id="name" class="{{errorCheck('name')}}">

si vous aviez une erreur, elle générerait ceci.

<input id="name" class="error">

vous pouvez l'utiliser pour définir votre style et vous savez maintenant les champs ont des erreurs. Malheureusement, vous ne savez pas qui est le premier champ.

une solution serait d'utiliser jQuery et the .premier filtre. Si vous empruntez cette route, allez à http://docs.angularjs.org/api/angular.element

une autre solution serait d'ajouter dans vos champs de formulaire un paramètre d'ordre de champ pour la fonction: {{errorCheck('name', 1)}. Vous pouvez pousser les noms des champs d'erreur à un tableau, puis les Trier par champ de paramètre d'ordre. Cela pourrait vous donner plus de flexibilité.

Espérons que cette aide.

0
répondu Darryl 2013-12-04 02:13:45

j'ai été inspiré par chaojidan ci-dessus pour suggérer cette variation pour ceux qui sont à l'aide de imbriquée angulaire 1.5.9 ng-formes:

class FormFocusOnErr implements ng.IDirective
{
    static directiveId: string = 'formFocusOnErr';

    restrict: string = "A";

    link = (scope: ng.IScope, elem, attrs) =>
    {
        // set up event handler on the form element
        elem.on('submit', function () {

            // find the first invalid element
            var firstInvalid = angular.element(
                elem[0].querySelector('.ng-invalid'))[0];

            // if we find one, set focus
            if (firstInvalid) {
                firstInvalid.focus();
                // ng-invalid appears on ng-forms as well as 
                // the inputs that are responsible for the errors.
                // In such cases, the focus will probably fail 
                // because we usually put the ng-focus attribute on divs 
                // and divs don't support the focus method
                if (firstInvalid.tagName.toLowerCase() === 'ng-form' 
                    || firstInvalid.hasAttribute('ng-form') 
                    || firstInvalid.hasAttribute('data-ng-form')) {
                    // Let's try to put a finer point on it by selecting 
                    // the first visible input, select or textarea 
                    // that has the ng-invalid CSS class
                    var firstVisibleInvalidFormInput = angular.element(firstInvalid.querySelector("input.ng-invalid,select.ng-invalid,textarea.ng-invalid")).filter(":visible")[0];
                    if (firstVisibleInvalidFormInput) {
                        firstVisibleInvalidFormInput.focus();
                    }
                }
            }
        });            
    }
}

// Register in angular app
app.directive(FormFocusOnErr.directiveId, () => new FormFocusOnErr());
0
répondu CAK2 2017-05-23 11:33:25

c'est parce que focus() n'est pas supporté en jqLite et à partir des docs angulaires sur l'élément.

0
répondu Acacio Martins 2017-06-28 12:09:06

une méthode sans directive pourrait ressembler à ceci. C'est ce que j'ai utilisé, depuis que j'ai un bouton "suivant" au bas de chaque page c'est effectivement dans l'index.html dans le pied de page. J'ai utiliser ce code dans le main.js.

if (!$scope.yourformname.$valid) {
      // find the invalid elements
      var visibleInvalids = angular.element.find('.ng-invalid:visible');


      if (angular.isDefined(visibleInvalids)){
        // if we find one, set focus
        visibleInvalids[0].focus();
      }

      return;
    }
-1
répondu CarComp 2015-06-26 15:25:59