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

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

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

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

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

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


person iandotkelly    schedule 04.12.2013    source источник


Ответы (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.

angular.module('myModule')
.controller('myController', ['$document', '$scope', function($document, $scope){
    // Code Here
}]);
person Sajan Mullappally    schedule 16.03.2016
comment
@iandotkelly Я также добавил метод без jquery .. надеюсь, что это поможет - person Sajan Mullappally; 17.03.2016
comment
angular.element($document[0].querySelector('input.ng-invalid'))[0].focus(); работал у меня. - person Manwal; 06.03.2017
comment
Я думаю, что мое решение в порядке, но я думаю, что эти идеи достойны галочки «принять». - person iandotkelly; 25.06.2019

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

Все, что мне было нужно, это директива для размещения самой формы с обработчиком событий, ищущим событие отправки. Затем он может перемещаться по DOM в поисках первого элемента с недопустимым классом .ng.

Пример использования 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, вы можете расширить пример, сделав ее директивой element (restrict: 'E'), и включить шаблон, который преобразует ее в. Однако это личное предпочтение.

person iandotkelly    schedule 05.12.2013
comment
elem уже является angular.element(), поэтому, если вы используете jQuery, по крайней мере, вы можете просто сделать elem.find('.ng-invalid:first').focus(). Не уверен, поддерживает ли jqLite также селектор :first. - person roryf; 18.12.2013
comment
Это можно было бы улучшить, сосредоточив внимание только на видимых элементах, например: var firstInvalid = element.find ('. Ng-invalid: visible'). First (); Есть несколько крайних случаев, когда это может иметь смысл. - person pcatre; 24.09.2014
comment
@pcatre - я также вижу, что вы внесли правку, которая была отклонена, предлагая изменить директиву атрибута «A». Конечно, директива A или даже C также подойдет, но почему директива E не сработала для вас? Кажется, у меня все работает нормально. - person iandotkelly; 24.09.2014
comment
@iandotkelly с помощью директивы атрибутов, мы можем сделать что-то вроде этого: ‹form available-form› ‹/form› С помощью директивы element, куда бы вы поместили элемент? Внутри формы? Что-то вроде ‹form› ‹accessible-form› ‹/accessible-form› ‹/form›? Или ваш ‹accessible-form› станет заменой элемента формы? - person pcatre; 24.09.2014
comment
@pcatre Да, ‹accessible-form›, который я создал, является заменой ‹form› и действительно преобразуется в ‹form› при запуске .... но вы правы, пример здесь не показывает всего этого. Так что я должен изменить это, как вы предлагаете - это лучший пример - спасибо. - person iandotkelly; 24.09.2014
comment
На самом деле это не так уж и плохо, как может показаться обход дома. QuerySelector - это собственный метод во всех современных браузерах, он хорошо оптимизируется и работает очень быстро. Намного быстрее, чем обход какой-либо коллекции объектов javascript. - person awe; 15.01.2015
comment
elem[0] возвращает вам элемент документа, а затем querySelector возвращает уже только первое совпадение. зачем оборачивать его в angular.element, а затем снова распаковывать с помощью [0]? - person danza; 31.03.2015
comment
создал из этого модуль angular и размещен на github - github.com/NithinBiliya/focus-invalid- поле - person Nithin Kumar Biliya; 25.05.2017

Вы можете создать директиву в качестве некоторых других ответов или, в качестве альтернативы, вы можете подключить ее с помощью 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;
  }
};
person nnattawat    schedule 30.10.2015
comment
angular.element($document[0].querySelector('input.ng-invalid'))[0].focus(); работал у меня - person Manwal; 06.03.2017

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

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

person Edmond Chui    schedule 02.04.2015
comment
И куда подевался этот код? А что, если мы не хотим, чтобы jQuery был частью решения? Суть вопроса не в том, как сфокусировать элемент (что можно было бы сделать с небольшим количеством JavaScript даже без jQuery) - дело в том, как мне это сделать, чтобы он был сфокусирован при нажатии на кнопку submit. недействительная форма. - person iandotkelly; 02.04.2015
comment
как добавить к этому анимацию при прокрутке? - person Sateesh Kumar Alli; 22.03.2017

Я некоторое время обдумывал эту идею и придумал собственное решение, оно может помочь людям, которые не любят сканировать 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();
                }
            });
        }
    }
});
person Digital Fu    schedule 20.02.2014
comment
Эй, я обязательно подробно рассмотрю это и попробую. Обход DOM кажется таким неэффективным. Если это сработает для меня, я переключу accept на это! - person iandotkelly; 07.05.2014

Я внес небольшие изменения в отличное решение, написанное 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();
                        });
                }
            });
        }
    };
});
person Mathemagician    schedule 26.10.2016
comment
Было бы очень полезно, если бы вы прикрепили plnkr или jsfiddle - person Ammar Hayder Khan; 07.05.2018

всего одна строка:

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

Вы можете добавить атрибут в каждый элемент формы, который является функцией (в идеале директивой), которая получает идентификатор поля. Этот идентификатор поля должен как-то коррелировать с вашим объектом $ 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)}}. Вы можете поместить имена полей ошибок в массив, а затем отсортировать их по параметру порядка полей. Это может дать вам больше гибкости.

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

person Darryl    schedule 04.12.2013
comment
Привет спасибо. У меня есть способ добавить атрибут к элементам формы, которые потерпели неудачу - я просто ищу самый Angular способ, и обход DOM в поисках этого атрибута кажется глупым, но, вероятно, единственным способом. - person iandotkelly; 05.12.2013

Меня вдохновил chaojidan выше, чтобы предложить этот вариант для тех, кто использует вложенные угловые ng-формы 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());
person CAK2    schedule 24.10.2016

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

person acacio.martins    schedule 27.06.2017
comment
Это комментарий к ответу CarComp, а не сам по себе ответ. - person iandotkelly; 27.06.2017

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

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

Метод, не основанный на директивах, может выглядеть так. Это то, что я использовал, поскольку у меня есть кнопка «Далее» внизу каждой страницы, которая на самом деле находится в 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;
    }
person CarComp    schedule 26.06.2015
comment
Прямолинейно и просто, именно так, как мне нравится - person cen; 06.10.2015
comment
используя встроенный jqLite angular, этот код не будет работать. Во-первых, обязательно вызывайте angular.element().find, а не angular.element.find, чтобы избежать исключения. Во-вторых, это будет возвращать только пустой массив из-за ограничений angular-jqLite. - person Patrick Fowler; 06.11.2015

person    schedule
comment
Привет. Было бы полезно, если бы вы объяснили, почему это лучший ответ. Я был бы счастлив сделать это принятым ответом, если бы у него было какое-то преимущество. - person iandotkelly; 23.10.2015