将焦点设置在AngularJs表单中的第一个无效输入上

时间:2013-12-04 01:28:21

标签: validation angularjs angularjs-directive wcag

我已经阅读了几篇与AngularJs焦点设置相关的文章和StackOverflow问题。

不幸的是,我读过的所有示例都假设我可以添加一些属性来获取焦点,例如一个focusMe directive

但是,如果我事先不知道将焦点设置为哪个输入怎么办?特别是如何将焦点设置为具有$ invalid set的表单中的第一个输入元素 - 即一个未通过验证的元素。可能有几个输入未通过验证,因此我无法使用仅基于此尝试调用.focus()的指令。 (我这样做是出于辅助功能/ WCAG的原因,这样做的好方法就是单击提交时最小化按键以找到第一个验证失败的字段)。

$ error对象将为所有未通过验证的控件提供帮助,但它们按照表单上任何外观顺序的失败类型进行分组。

我确信我能想出一些这样做的方法。表单上的指令,当需要设置焦点时接收一些广播 - 该指令然后可以搜索第一个$ invalid元素。然而,这似乎非常复杂,我想知道这些是否是更好的“角度”方式。

13 个答案:

答案 0 :(得分:87)

好的,所以答案比我想象的要简单。

我需要的只是一个指令来放置表单本身,事件处理程序查找提交事件。然后,这可以遍历DOM,查找其上具有.ng-invalid类的第一个元素。

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

此处的示例使用Attribute指令,您可以展开示例以使其成为元素指令(restrict:'E')并包含将其转换为a的模板。然而,这是个人偏好。

答案 1 :(得分:15)

您可以创建指令作为其他一些答案,或者您可以将其与ng-submit挂钩并在控制器中实现逻辑。

查看:

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

控制器:

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

答案 2 :(得分:13)

您也可以使用angular.element

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

查看

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

<强>控制器

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

使用ngMessages进行验证

没有jquery方式

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

使用此方法时,需要在角度控制器

中传递$document作为参数
angular.module('myModule')
.controller('myController', ['$document', '$scope', function($document, $scope){
    // Code Here
}]);

答案 3 :(得分:7)

您可以使用纯jQuery选择第一个无效输入:

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

答案 4 :(得分:4)

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

答案 5 :(得分:2)

我一直在玩这个想法,我提出了自己的解决方案,它可能会帮助那些不喜欢抓住DOM的人,比如我。

据我所知,表单元素以一致的顺序(即从上到下)注册自己,并且通过表单名称(例如$ scope.myForm),它们的名称和验证状态在范围内可用。 / p>

这让我觉得有一种方法可以找到第一个无效的表单输入,而无需抓取DOM,而是抓取角度js的内部结构。下面是我的解决方案,但它假设您有一些其他方式来聚焦表单元素,我正在广播到自定义指令,如果广播匹配它将自己关注的元素的名称(当你到达时,这本身是有用的控制哪个元素专注于第一次加载。)

查找第一个无效的功能(理想情况下通过服务与控制器共享)

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

自定义焦点指令

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

答案 6 :(得分:1)

我对iandotkelly写的伟大解决方案进行了一些小修改。 此解决方案添加了一个在滚动时触发的动画,并在此之后聚焦到所选元素。

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

答案 7 :(得分:1)

只有一行:

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

答案 8 :(得分:0)

您可以在每个表单元素中添加一个属性,该表单元素是一个接收字段ID的函数(理想情况下是一个指令)。此字段ID必须以某种方式与您的$ error对象相关联。该函数可以检查id是否在$ error对象中,如果是,则返回错误的属性设置。

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

如果您遇到错误,则会产生此错误。

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

您可以使用它来设置样式,现在您知道哪些字段有错误。不幸的是你不知道哪个是第一个字段。

一种解决方案是使用jQuery和.first过滤器。如果你走这条路,请查看http://docs.angularjs.org/api/angular.element

另一种解决方案是在表单字段中添加函数的字段顺序参数:{{errorCheck('name',1)}}。您可以将错误字段名称推送到数组,然后按字段顺序参数对它们进行排序。这可以为您提供更大的灵活性。

希望这有帮助。

答案 9 :(得分:0)

我受到上面chaojidan的启发,为那些使用嵌套角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());

答案 10 :(得分:0)

这是因为jqLit​​e和元素上的Angular文档不支持focus()

答案 11 :(得分:0)

对@Sajan所说的内容进行细微调整,

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

答案 12 :(得分:-1)

基于非指令的方法可能如下所示。这是我使用的,因为我在页面的底部有一个“下一个”按钮实际上在页脚的index.html中。我在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;
    }