Установить фокус на первый неверный ввод в форме AngularJs

Я прочитал несколько статей и вопросов Stackru, касающихся настройки фокуса в AngularJs.

К сожалению, все примеры, которые я прочитал, предполагают, что есть некоторый атрибут, который я могу добавить к элементу, чтобы получить фокус, например, директива focusMe.

Однако что, если я не знаю заранее, на какой вход установить фокус? В частности, как мне установить фокус на первый элемент ввода в форме, в которой установлен $ invalid - то есть элемент, который не проходит проверку. Может быть несколько входов, которые не проходят проверку, поэтому я не могу использовать директиву, которая просто пытается вызвать.focus(), основываясь на этом. (Я делаю это по причинам доступности /WCAG, поэтому рекомендуется делать так, чтобы при отправке нажимали, чтобы минимизировать нажатия клавиш, чтобы найти первое поле, которое не прошло проверку).

Объект $ error выдаст все элементы управления, которые не прошли проверку, но они сгруппированы по типу ошибки, а не в порядке появления в форме.

Я уверен, что смогу придумать какой-нибудь хитрый способ сделать это. Директива в форме, которая получает некоторую рассылку, когда необходимо установить фокус - эта директива может затем искать первый элемент $ invalid. Однако это кажется очень сложным, и я хотел бы знать, является ли это лучшим, более "угловатым" способом сделать это.

12 ответов

Решение

Итак, ответ был проще, чем я думал.

Все, что мне было нужно, - это указание на саму форму, с обработчиком события, ищущим событие submit. Затем он может пройти через 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. Это, однако, личное предпочтение.

Вы также можете использовать 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
}]);

Вы можете создать директиву, как некоторые другие ответы, или вы можете подключить ее 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;
  }
};

Вы можете использовать чистый jQuery для выбора первого неверного ввода:

$('input.ng-invalid').first().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();
                    }
                });
            }
        };
    })

Некоторое время я играл с этой идеей и придумал свое собственное решение, оно может помочь людям, которые не любят сканировать DOM, таким как я.

Насколько я могу судить, элементы формы регистрируются в согласованном порядке (т. Е. Сверху вниз), и их имена и состояния проверки доступны в области видимости через любое имя формы (например, $scope.myForm).

Это привело меня к мысли, что был способ найти первый неверный ввод формы, не сканируя 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();
                }
            });
        }
    }
});

Я сделал несколько небольших модификаций для отличного решения, написанного 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();
                        });
                }
            });
        }
    };
});

Только одна строка:

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

Я был вдохновлен chaojidan выше, чтобы предложить этот вариант для тех, кто использует вложенные угловые формы 1.5.9 нг:

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

Небольшая настройка, которую сказал @Sajan, сработала для меня,

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

Вы можете добавить атрибут в каждый элемент формы, который является функцией (в идеале директивой), которая получает идентификатор поля. Этот идентификатор поля должен как-то коррелировать с вашим объектом $ error. Функция может проверить, есть ли идентификатор в вашем объекте $ error, и если это так, вернуть настройку атрибута для ошибки.

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

Если бы у вас была ошибка, это сгенерирует это.

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

Вы можете использовать это, чтобы установить свой стиль, и теперь вы знаете, в каких полях есть ошибки. К сожалению, вы не знаете, какое поле является первым.

Одним из решений будет использование jQuery и фильтра.first. Если вы идете по этому пути, проверьте http://docs.angularjs.org/api/angular.element

Другим решением было бы добавить в поля формы параметр порядка полей для функции: {{errorCheck('name', 1)}}. Вы можете поместить имена полей ошибок в массив, а затем отсортировать их по параметру порядка полей. Это может дать вам больше гибкости.

Надеюсь это поможет.

Это потому что focus() не поддерживается в jqLite и в документах Angular по элементам.

Ненаправленный метод может выглядеть следующим образом. Это то, что я использовал, так как у меня есть кнопка "Далее" внизу каждой страницы, которая на самом деле находится в 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;
    }
Другие вопросы по тегам