61

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

不幸的是,我读过的所有示例都假设有一些属性可以添加到元素中以获得焦点,例如focusMe 指令

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

$error 对象将提供所有未通过验证的控件,但它们按失败类型分组,而不是按表单上出现的任何顺序。

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

4

13 回答 13

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 指令,您可以扩展示例以使其成为元素指令(限制:'E')并包含一个将其转换为 . 不过这是个人喜好。

于 2013-12-05T01:06:15.403 回答
17

您也可以使用 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
}]);
于 2016-03-16T15:20:52.440 回答
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;
  }
};
于 2015-10-30T02:05:50.373 回答
7

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

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

于 2015-04-02T05:28:46.457 回答
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();
                    }
                });
            }
        };
    })

于 2015-10-23T10:43:16.003 回答
2

我一直在玩这个想法,并提出了自己的解决方案,它可能会帮助像我这样不喜欢爬取 DOM 的人。

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

这让我想到有一种方法可以找到第一个无效的表单输入,而无需爬取 DOM,而是爬取 Angular 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();
                }
            });
        }
    }
});
于 2014-02-20T04:52:50.183 回答
1

只有一行:

if($scope.formName.$valid){
    //submit
}
else{
    $scope.formName.$error.required[0].$$element.focus();
}
于 2017-10-27T07:50:40.413 回答
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();
                        });
                }
            });
        }
    };
});
于 2016-10-26T08:45:34.033 回答
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)}}。您可以将错误字段名称推送到数组,然后按字段顺序参数对它们进行排序。这可以给你更多的灵活性。

希望这可以帮助。

于 2013-12-04T02:13:45.377 回答
0

@Sajan 所说的对我有用的小调整,

angular.element("[name='" + this.formName.$name + "']").find('.ng-invalid:visible:first')[0].focus();
于 2019-06-25T17:55:17.070 回答
0

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

于 2017-06-27T18:14:56.090 回答
0

我受到上述chaojidan的启发,为那些使用嵌套 angular 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());
于 2016-10-24T22:04:40.000 回答
-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;
    }
于 2015-06-26T15:25:59.023 回答