Установить фокус на первый неверный ввод в форме 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;
}