web-dev-qa-db-fra.com

Définir le focus sur la première entrée non valide dans le formulaire AngularJs

J'ai lu plusieurs articles et questions StackOverflow concernant la définition de la focalisation dans AngularJs.

Malheureusement, tous les exemples que j'ai lus supposent qu'il existe un attribut que je peux ajouter à l'élément pour obtenir le focus, par exemple. une directive focusMe .

Cependant, que se passe-t-il si je ne sais pas à l'avance sur quelle entrée mettre le focus? En particulier, comment définir le focus sur le premier élément d’entrée d’un formulaire contenant $ invalid set, c’est-à-dire un élément dont la validation échoue. Il se peut que plusieurs entrées échouent à la validation. Par conséquent, je ne peux pas utiliser une directive qui tente simplement d'appeler .focus () en fonction de cela. (Je le fais pour des raisons d'accessibilité/WCAG, il est recommandé de cliquer sur ce bouton pour minimiser les pressions sur les touches afin de trouver le premier champ pour lequel la validation a échoué.).

L'objet $ error donnera tous les contrôles qui échouent à la validation, mais ils sont regroupés par type d'échec, sans ordre d'apparition dans le formulaire.

Je suis sûr que je peux trouver un moyen malavisé de le faire. Une directive du formulaire, qui reçoit une diffusion lorsque le focus est défini - cette directive peut alors rechercher le premier élément $ invalid. Cependant, cela semble très complexe et j'aimerais savoir s'il s'agit d'un moyen plus «angulaire» de le faire.

59
iandotkelly

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

Tout ce dont j'avais besoin était une directive à mettre sur le formulaire lui-même, avec un gestionnaire d'événements recherchant l'événement submit. Cela peut ensuite traverser le DOM à la recherche du premier élément contenant la classe .ng-invalide.

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 utilisé ici utilise une directive Attribute. Vous pouvez développer cet exemple pour qu'il s'agisse d'une directive d'élément (restrict: 'E') et inclure un modèle qui le convertit en a. C'est cependant une préférence personnelle.

87
iandotkelly

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

Vue:

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

Manette:

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

Vous pouvez également utiliser angular.element

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

Vue

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

Manette

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

ngMessages utilisé pour la validation

La manière sans jquery

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

Lorsque vous utilisez cette méthode, vous devez passer $document comme paramètre dans votre contrôleur angulaire.

angular.module('myModule')
.controller('myController', ['$document', '$scope', function($document, $scope){
    // Code Here
}]);
12
Sajan Mullappally

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

Cela fait un moment que je joue avec cette idée et j'ai proposé ma propre solution: elle pourrait aider les personnes défavorisées à explorer le DOM, comme moi.

Autant que je sache, les éléments de formulaire s'enregistrent dans un ordre cohérent (c'est-à-dire de haut en bas) et leurs noms et états de validation sont disponibles dans la portée, quel que soit le nom du formulaire (par exemple, $ scope.myForm).

Cela m'a amené à penser qu'il y avait un moyen de trouver la première entrée de formulaire invalide sans explorer le DOM mais plutôt explorer les structures internes de js angulaire. Voici ma solution, mais elle suppose que vous avez un autre moyen de focaliser les éléments de formulaire. Je diffuse une directive personnalisée. Si la diffusion correspond au nom de l'élément sur lequel elle va se concentrer (ce qui est utile en soi lorsque vous arrivez à contrôle quel élément se concentre sur le premier chargement).

La fonction pour trouver le premier invalide (idéalement partagée avec les contrôleurs via un service)

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

Et la directive de mise au point personnalisée

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

J'ai apporté quelques petites modifications à l'excellente solution écrite par iandotkelly . Cette solution ajoute une animation déclenchée par le défilement, puis active l'élément sélectionné.

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
Mathemagician

juste une ligne: 

if($scope.formName.$valid){
    //submit
}
else{
    $scope.formName.$error.required[0].$$element.focus();
}
1
sonphuong

En effet, focus() n'est pas pris en charge par jqLite et par l'élément Angular docs on.

0
Acacio Martins

Un tweak mineur avec ce que @Sajan a dit a fonctionné pour moi,

angular.element("[name='" + this.formName.$name + "']").find('.ng-invalid:visible:first')[0].focus();
0
Yogaraj Saravanan

Chaojidan ci-dessus m'a inspiré pour suggérer cette variation à ceux qui utilisent des formes angulaires imbriquées de 1.5.9 ng:

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
CAK2

Vous pouvez ajouter un attribut dans chaque élément de formulaire qui est une fonction (idéalement une directive) qui reçoit un identifiant de champ. Cet identifiant de champ devrait être en quelque sorte corrélé à votre objet $ error. La fonction peut vérifier si l'ID est dans votre objet $ error et, le cas échéant, renvoyer le paramètre d'attribut pour une erreur. 

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

Si vous aviez une erreur, cela générerait ceci.

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

Vous pouvez utiliser ceci pour définir votre style et vous savez maintenant quels champs contiennent des erreurs. Malheureusement, vous ne savez pas quel est le premier champ. 

Une solution serait d’utiliser jQuery et le filtre .first. Si vous choisissez cette voie, consultez http://docs.angularjs.org/api/angular.element

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

J'espère que cela t'aides.

0
Darryl