reloadwithdebuginfo prod deploy validation angularjs angularjs-directive wcag

validation - prod - Establecer el foco en la primera entrada no válida en forma de AngularJs



angularjs production (12)

Esto se debe a que focus() no es compatible con jqLite y desde el elemento Angular docs on.

He leído varios artículos y preguntas sobre StackOverflow relacionadas con la configuración del foco en AngularJs.

Lamentablemente, todos los ejemplos que he leído suponen que hay algún atributo que puedo agregar al elemento para obtener el foco, por ejemplo, una directiva focusMe .

Sin embargo, ¿qué ocurre si no sé de antemano a qué entrada se debe enfocar? En particular, ¿cómo configuro el foco en el primer elemento de entrada en un formulario que tiene $ invalid set, es decir, un elemento que falla la validación? Puede haber varias entradas que fallen la validación, por lo que no puedo usar una directiva que simplemente intente llamar a .focus () en función de esto. (Estoy haciendo esto por razones de accesibilidad / WCAG, es una buena práctica hacerlo al enviar clic para minimizar las pulsaciones de teclas para encontrar el primer campo que ha fallado la validación).

El objeto $ error dará todos los controles que fallen la validación, pero se agrupan por el tipo de falla, sin ningún orden de aparición en el formulario.

Estoy seguro de que puedo llegar a una forma simplificada de hacer esto. Una directiva en el formulario, que recibe alguna transmisión cuando se necesita establecer el foco, esa directiva puede buscar el primer elemento $ invalid. Sin embargo, esto parece muy complejo y me gustaría saber si se trata de una forma mejor y más "angular" de hacer esto.


He estado jugando con esta idea por un tiempo y se me ocurrió mi propia solución, puede ayudar a las personas que son adversas a rastrear el DOM, como yo.

Por lo que puedo decir, los elementos de formulario se registran en un orden coherente (es decir, de arriba abajo) y sus nombres y estados de validación están disponibles en el alcance a través del nombre del formulario (ej. $ Scope.myForm).

Esto me llevó a pensar que había una manera de encontrar la primera entrada de formulario inválido sin arrastrar el DOM y en su lugar arrastrar las estructuras internas de js angulares. A continuación está mi solución, pero asume que tienes otra forma de enfocar los elementos del formulario, estoy transmitiendo a una directiva personalizada, si la transmisión coincide con el nombre del elemento se enfocará (lo cual es útil en sí mismo a medida que llegas a controlar qué elemento toma enfoque en la primera carga).

La función para encontrar el primer inválido (idealmente compartido con los controladores a través de un servicio)

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

Y la directiva de enfoque personalizado

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


Hice algunas pequeñas modificaciones a la gran solución escrita por iandotkelly. Esta solución agrega una animación que se desencadena en desplazamiento y luego enfoca el elemento seleccionado.

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


Me inspiré en el anterior para sugerir esta variación para aquellos que están utilizando ángulos anidados 1.5.9 ng-forms:

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


Ok, entonces la respuesta fue más simple de lo que pensaba.

Todo lo que necesitaba era una directiva para poner el formulario en sí, con un controlador de eventos buscando el evento de envío. Esto puede atravesar el DOM buscando el primer elemento que tiene la clase .ng inválida.

Ejemplo usando 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(); } }); } }; });

El ejemplo aquí utiliza una directiva de atributo, puede expandir el ejemplo para que sea una directiva de elementos (restringir: ''E'') e incluir una plantilla que convierta esto en a. Sin embargo, esta es una preferencia personal.


Puede agregar un atributo en cada elemento de formulario que es una función (idealmente una directiva) que recibe una identificación de campo. Este identificador de campo tendría que correlacionarse de alguna manera con su objeto $ error. La función puede verificar si la identificación está en su objeto $ error, y si es así, regrese la configuración del atributo para un error.

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

Si tuvieras un error, generaría esto.

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

Puede usar esto para configurar su estilo y ahora sabe qué campos tienen errores. Desafortunadamente no sabes cuál es el primer campo.

Una solución sería usar jQuery y el primer filtro. Si sigue esta ruta, visite http://docs.angularjs.org/api/angular.element

Otra solución sería agregar a sus campos de formulario un parámetro de orden de campo para la función: {{errorCheck (''name'', 1)}}. Puede insertar los nombres de los campos de error en una matriz, luego ordenarlos por el parámetro de orden de campo. Esto podría darte más flexibilidad.

Espero que esto ayude.


Puede crear directivas como algunas otras respuestas o alternativamente puede conectarlas con ng-submit e implementar lógica en el controlador.

Ver:

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

Controlador:

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


Puede usar jQuery puro para seleccionar la primera entrada no válida:

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


También puede usar angular.element

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

Ver

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

Controlador

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

utiliza ngMessages para validación

La manera no jquery

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

Al usar este método, necesita pasar $document como parámetro en su controlador angular

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


Un método basado en no directiva podría verse así. Es lo que utilicé, ya que tengo el botón ''siguiente'' en la parte inferior de cada página que está realmente en index.html en el pie de página. Yo uso este código en 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; }


solo una linea

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


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