Вывод ошибок валидации пользователю в JavaScript
Сообщение об ошибке должно не только остановить отправку формы, но и помочь пользователю исправить значение. Для этого текст обычно выводят рядом с проблемным полем, само поле отмечают как невалидное, а после отправки переводят фокус к первой найденной ошибке.
Браузер умеет показывать стандартные подсказки, но их внешний вид и текст ограниченно настраиваются. Если интерфейсу нужны собственные сообщения, автоматическую проверку при отправке отключают и выводят ошибки через JavaScript.
- Каким должно быть сообщение об ошибке
- Разметка формы и областей ошибок
- Стили невалидных полей
- Получение текста ошибки
- Показ и очистка ошибки
- Собственное правило проверки
- Когда показывать ошибки
- Проверка при отправке формы
- Вывод ошибки от сервера
- Итого
Каким должно быть сообщение об ошибке
Хорошее сообщение объясняет, что именно нужно изменить. Фраза «Введите не менее 2 символов» полезнее, чем общее «Неверное значение».
При выводе ошибки важно выполнить несколько условий:
- расположить сообщение рядом с соответствующим полем;
- связать поле и сообщение в разметке;
- не обозначать проблему только цветом;
- не удалять введённые пользователем данные;
- убирать ошибку после исправления значения.
Показывать все ошибки сразу при открытии пустой формы обычно не стоит. Пользователь ещё не успел ничего сделать. Удобнее проверять отдельное поле после потери фокуса, а всю форму - после попытки отправки.
Разметка формы и областей ошибок
Создадим небольшую форму регистрации:
<form class="account-form" novalidate>
<div class="form-field">
<label for="account-name">Имя</label>
<input
id="account-name"
name="username"
type="text"
minlength="2"
aria-describedby="account-name-error"
required
>
<p
id="account-name-error"
class="field-error"
aria-live="polite"
></p>
</div>
<div class="form-field">
<label for="account-email">Электронная почта</label>
<input
id="account-email"
name="email"
type="email"
aria-describedby="account-email-error"
required
>
<p
id="account-email-error"
class="field-error"
aria-live="polite"
></p>
</div>
<div class="form-field">
<input
id="account-agreement"
name="agreement"
type="checkbox"
aria-describedby="account-agreement-error"
required
>
<label for="account-agreement">Я принимаю условия</label>
<p
id="account-agreement-error"
class="field-error"
aria-live="polite"
></p>
</div>
<button type="submit">Создать аккаунт</button>
</form>
<p class="form-status" aria-live="polite"></p>
Для каждого поля заранее создан пустой элемент p. Атрибут aria-describedby связывает поле с текстом ошибки по его id. Благодаря этому вспомогательные технологии могут сообщить пользователю не только подпись поля, но и описание проблемы.
aria-live=“polite” позволяет озвучить появившееся сообщение без немедленного прерывания другого текста. Общая область form-status будет сообщать результат проверки всей формы.
Атрибут novalidate отключает стандартные всплывающие подсказки и автоматическую блокировку отправки. Методы checkValidity() и свойства validity при этом продолжают работать. Использовать novalidate следует только вместе с собственным понятным интерфейсом ошибок.
Стили невалидных полей
JavaScript будет добавлять полю атрибут aria-invalid=“true”. По нему можно изменить оформление:
.field-error {
min-height: 1.5em;
margin: 4px 0 12px;
color: #b42318;
}
input[aria-invalid="true"] {
border: 2px solid #b42318;
}
Красная рамка помогает заметить поле визуально, но рядом остаётся текст с причиной ошибки. Поэтому результат не зависит только от восприятия цвета.
Получение текста ошибки
Получим форму, её проверяемые поля и общую область статуса:
const accountForm = document.querySelector('.account-form');
const formFields = accountForm.querySelectorAll('input');
const formStatus = document.querySelector('.form-status');
Причина невалидности находится в объекте field.validity. Создадим функцию, которая возвращает подходящее сообщение:
function getErrorMessage(field) {
const validity = field.validity;
if (validity.customError) {
return field.validationMessage;
}
if (validity.valueMissing) {
if (field.type === 'checkbox') {
return 'Подтвердите согласие с условиями';
}
if (field.type === 'email') {
return 'Введите электронную почту';
}
return 'Введите имя';
}
if (validity.typeMismatch) {
return 'Введите адрес в формате name@example.com';
}
if (validity.tooShort) {
return 'Введите не менее ' + field.minLength + ' символов';
}
return '';
}
Сначала проверяется customError, потому что такое сообщение создаёт собственное правило JavaScript. Затем функция обрабатывает пустое значение, неверный тип и слишком короткий текст.
Функция учитывает поля из текущего примера. Для формы с числами, датами или pattern можно добавить проверки rangeUnderflow, rangeOverflow и patternMismatch.
Показ и очистка ошибки
У элементов ошибок есть предсказуемые идентификаторы: к id поля добавлена строка -error. Используем это соглашение, чтобы найти нужный элемент:
function showFieldError(field) {
const errorElement = document.getElementById(field.id + '-error');
const message = getErrorMessage(field);
if (!errorElement) {
return;
}
errorElement.textContent = message;
if (message) {
field.setAttribute('aria-invalid', 'true');
} else {
field.removeAttribute('aria-invalid');
}
}
Если сообщение не пустое, функция выводит его и отмечает поле атрибутом aria-invalid=“true”. Когда значение становится валидным, textContent получает пустую строку, а атрибут удаляется.
Для обычного текста используйте textContent. Передавать сообщение от сервера в innerHTML небезопасно: строка может быть разобрана браузером как HTML-разметка.
Собственное правило проверки
Атрибут required считает строку из пробелов заполненной. Добавим правило, которое не разрешает использовать пробелы вместо имени:
function applyCustomRules(field) {
field.setCustomValidity('');
if (field.name === 'username') {
const containsOnlySpaces = (
field.value !== '' && field.value.trim() === ''
);
if (containsOnlySpaces) {
field.setCustomValidity('Имя не может состоять только из пробелов');
}
}
}
В начале функция передаёт в setCustomValidity() пустую строку и тем самым очищает предыдущую пользовательскую ошибку. Если новое значение нарушает правило, функция устанавливает актуальное сообщение.
Очищать старую ошибку обязательно. Пока в setCustomValidity() хранится непустая строка, поле остаётся невалидным даже после изменения value.
Когда показывать ошибки
Добавим проверку после потери фокуса. Если ошибка уже показана, будем обновлять её во время исправления:
formFields.forEach(function (field) {
field.addEventListener('blur', function () {
applyCustomRules(field);
showFieldError(field);
});
field.addEventListener('input', function () {
applyCustomRules(field);
if (field.hasAttribute('aria-invalid')) {
showFieldError(field);
}
});
});
Первый обработчик не показывает ошибку до того, как пользователь закончит работу с полем и покинет его. Второй не начинает проверку слишком рано, но обновляет уже видимое сообщение. Как только значение станет валидным, функция очистит текст и оформление.
Событие input также происходит при переключении checkbox. Поэтому сообщение о непринятых условиях исчезнет сразу после установки флажка.
Проверка при отправке формы
При попытке отправки нужно проверить все поля, даже если пользователь не переходил между ними:
accountForm.addEventListener('submit', function (event) {
event.preventDefault();
formFields.forEach(function (field) {
applyCustomRules(field);
});
const isFormValid = accountForm.checkValidity();
formFields.forEach(function (field) {
showFieldError(field);
});
if (!isFormValid) {
formStatus.textContent = 'Исправьте ошибки в форме';
const firstInvalidField = accountForm.querySelector(':invalid');
firstInvalidField?.focus();
return;
}
formStatus.textContent = 'Данные прошли проверку';
});
checkValidity() возвращает общий результат и сам не показывает стандартные подсказки. Атрибут novalidate также отключает их автоматический показ при отправке, поэтому после проверки код выводит собственное сообщение для каждого поля.
Если найдена ошибка, querySelector(‘:invalid’) получает первое невалидное поле, а focus() переводит к нему фокус. Пользователь сразу оказывается в месте, с которого удобно начать исправление.
В учебном примере форма не отправляется, поэтому preventDefault() вызывается всегда. В реальном интерфейсе после успешной проверки можно собрать данные и отправить их на сервер.
Вывод ошибки от сервера
Некоторые данные можно надёжно проверить только на сервере. Например, сервер может сообщить, что адрес электронной почты уже используется.
Такую ошибку можно вывести через тот же интерфейс:
const emailInput = accountForm.elements.email;
emailInput.setCustomValidity('Этот адрес уже зарегистрирован');
showFieldError(emailInput);
formStatus.textContent = 'Исправьте ошибки в форме';
emailInput.focus();
После следующего изменения поля обработчик input вызовет applyCustomRules(), очистит серверное сообщение и заново проверит текущее значение.
Не очищайте всю форму после ответа с ошибкой. Пользователь должен сохранить остальные введённые данные и исправить только проблемное поле.
Итого
Для собственного интерфейса ошибок заранее создайте области сообщений и свяжите их с полями через aria-describedby. Невалидное поле отмечайте с помощью aria-invalid=“true”, а текст выводите через textContent.
Отдельное поле удобно проверять после blur, уже показанную ошибку - обновлять во время input, а при submit - проверять форму целиком и переводить фокус к первой проблеме. Пользовательские и серверные ошибки можно задавать через setCustomValidity(), обязательно очищая их после изменения значения.