Валидация формы в JavaScript

Валидация формы - это проверка введённых данных перед их обработкой или отправкой. Например, обязательное поле не должно быть пустым, адрес электронной почты должен иметь подходящий формат, а два введённых пароля должны совпадать.

Часть правил можно задать прямо в HTML. JavaScript дополняет их, когда нужно запустить проверку из кода, узнать причину ошибки или проверить условие, которое нельзя выразить одним HTML-атрибутом.

Встроенная и пользовательская валидация

Для проверки формы обычно используют два уровня:

  • HTML-ограничения проверяют стандартные случаи: пустое поле, формат электронной почты, длину строки, диапазон числа;
  • JavaScript проверяет правила приложения: совпадение паролей, допустимость сочетания полей или другие условия.

Не стоит полностью переносить в JavaScript то, что браузер уже умеет проверять через HTML. Атрибут required понятнее ручной проверки пустого значения, а type=“email” сразу сообщает браузеру назначение поля.

Ограничения в HTML

Создадим форму регистрации:

<form class="registration-form">
  <label for="username">Имя</label>
  <input
    id="username"
    name="username"
    type="text"
    minlength="2"
    maxlength="30"
    required
  >

  <label for="email">Электронная почта</label>
  <input id="email" name="email" type="email" required>

  <label for="age">Возраст</label>
  <input id="age" name="age" type="number" min="14" max="120">

  <label for="password">Пароль</label>
  <input
    id="password"
    name="password"
    type="password"
    minlength="8"
    required
  >

  <label for="password-confirmation">Повторите пароль</label>
  <input
    id="password-confirmation"
    name="passwordConfirmation"
    type="password"
    required
  >

  <label>
    <input name="agreement" type="checkbox" required>
    Я принимаю условия
  </label>

  <button type="submit">Зарегистрироваться</button>
</form>

<p class="registration-status" aria-live="polite"></p>
const registrationForm = document.querySelector('.registration-form');
const registrationStatus = document.querySelector('.registration-status');

В примере браузер уже может проверить большую часть данных:

АтрибутЧто проверяет
requiredЗначение обязательно или checkbox должен быть отмечен
type=“email”Строка имеет допустимый для браузера формат электронной почты
minlength и maxlengthДлина текста находится в заданных пределах
min и maxЧисло или дата входят в допустимый диапазон
stepЗначение соответствует заданному шагу
patternТекст соответствует регулярному выражению

Если поле необязательное и осталось пустым, ограничения формата обычно не создают ошибку. Например, пустое поле возраста допустимо, потому что у него нет required.

Атрибут required считает строку из пробелов заполненным значением. Если в конкретном поле такой ввод недопустим, его дополнительно проверяют в JavaScript с помощью trim().

Проверка при отправке формы

Браузер автоматически проверяет ограничения перед событием submit. Если форма невалидна, он отменяет отправку, показывает сообщение и переводит фокус к проблемному полю. Обработчик submit в этом случае не запускается.

Когда все встроенные ограничения выполнены, запускается обработчик submit. В нём можно проверить дополнительные правила приложения, получить данные и отправить их.

Не переносите проверку только в обработчик click на кнопке. Работайте с событием submit формы, чтобы учесть отправку мышью, с клавиатуры и стандартные HTML-ограничения.

checkValidity() и reportValidity()

Иногда форму нужно проверить из JavaScript до отправки. Метод checkValidity() возвращает true, если все поля прошли проверку, и false, если найдена ошибка:

const isFormValid = registrationForm.checkValidity();

console.log(isFormValid); // true или false

Метод не показывает стандартные сообщения браузера. Если нужно не только получить результат, но и показать пользователю ошибку, используют reportValidity():

const isFormValid = registrationForm.reportValidity();

console.log(isFormValid); // true или false

reportValidity() проверит форму, покажет стандартное сообщение у первого неподходящего поля и вернёт результат.

Оба метода доступны и отдельным элементам:

const emailInput = registrationForm.elements.email;

console.log(emailInput.checkValidity());
emailInput.reportValidity();

Для программной отправки с обычной проверкой используют requestSubmit(). Метод form.submit() отправляет форму напрямую и обходит встроенную валидацию и событие submit.

Свойство validity

Свойство validity содержит объект ValidityState. Его свойства помогают определить, какое именно правило не выполнено.

const emailInput = registrationForm.elements.email;

console.log(emailInput.validity.valid);
console.log(emailInput.validity.valueMissing);
console.log(emailInput.validity.typeMismatch);

Основные свойства ValidityState:

СвойствоПричина ошибки
valueMissingОбязательное поле не заполнено
typeMismatchЗначение не соответствует типу, например формату email
tooShort или tooLongНарушено ограничение длины
rangeUnderflow или rangeOverflowЧисло или дата выходят за пределы min и max
stepMismatchЗначение не соответствует step
patternMismatchЗначение не соответствует pattern
customErrorОшибка задана через setCustomValidity()
validВсе применимые проверки пройдены

Каждое свойство, кроме valid, становится равным true, когда обнаружена соответствующая проблема. У одного поля может быть несколько причин невалидности, поэтому порядок показа сообщений определяет ваш код или браузер.

Сообщение validationMessage

Свойство validationMessage содержит готовый текст ошибки. Для стандартных ограничений браузер формирует и переводит его самостоятельно:

const message = emailInput.validationMessage;

console.log(message);

Если поле валидно, свойство возвращает пустую строку. Точный текст зависит от браузера, языка системы и нарушенного правила. Поэтому не стоит сравнивать validationMessage с заранее записанной фразой. Причину ошибки проверяют через validity.

Собственное правило через setCustomValidity()

HTML не умеет сравнивать два поля. Проверим через JavaScript, совпадают ли пароль и его подтверждение:

const passwordInput = registrationForm.elements.password;
const confirmationInput = registrationForm.elements.passwordConfirmation;

function validatePasswordConfirmation() {
  const hasConfirmation = confirmationInput.value !== '';
  const passwordsDiffer = (
    hasConfirmation && confirmationInput.value !== passwordInput.value
  );

  confirmationInput.setCustomValidity(
    passwordsDiffer ? 'Пароли не совпадают' : '',
  );
}

passwordInput.addEventListener('input', validatePasswordConfirmation);
confirmationInput.addEventListener('input', validatePasswordConfirmation);

Если пароли различаются, setCustomValidity() сохраняет сообщение и делает поле невалидным. Когда ошибка исправлена, в метод обязательно передаётся пустая строка. Иначе customError останется равным true, и форма продолжит считаться невалидной.

Пустое подтверждение в функции не получает собственную ошибку, потому что этот случай уже проверяет атрибут required.

Проверка запускается при вводе в каждое из двух полей. Это важно: если пользователь изменит исходный пароль после заполнения подтверждения, состояние сразу обновится.

Пример проверки формы

Объединим пользовательское правило с обработкой отправки:

registrationForm.addEventListener('submit', function (event) {
  validatePasswordConfirmation();

  if (!registrationForm.reportValidity()) {
    event.preventDefault();
    return;
  }

  event.preventDefault();
  registrationStatus.textContent = 'Данные можно отправлять';
});

Сначала функция обновляет состояние подтверждения пароля. Затем reportValidity() повторно проверяет форму и показывает ошибку, если собственное правило не выполнено.

Браузер выполняет первоначальную проверку ещё до submit. Поэтому при пустом обязательном поле обработчик вообще не запустится. Повторный вызов нужен для правила, которое код обновляет непосредственно внутри обработчика.

В примере стандартная отправка отменяется и после успешной проверки, потому что серверного обработчика нет. Если форма должна отправляться обычным способом, последний event.preventDefault() не нужен. При отправке через fetch() на его месте выполняют сетевой запрос.

Когда нужен novalidate

Атрибут novalidate у формы отключает автоматический показ сообщений и блокировку отправки браузером:

<form class="registration-form" novalidate>
  <!-- поля формы -->
</form>

Он нужен, если интерфейс полностью самостоятельно показывает ошибки рядом с полями. В таком случае обработчик submit должен вызвать checkValidity(), вывести сообщения, перевести фокус к первому ошибочному полю и остановить обработку невалидных данных.

Сам атрибут не делает форму валидной и не отключает методы проверки. checkValidity(), свойства validity и validationMessage продолжат работать.

Не добавляйте novalidate, если собственный интерфейс ошибок не реализован. Иначе пользователь сможет запустить отправку формы, не получив понятного сообщения о проблеме.

Проверка на сервере

Валидация в браузере помогает пользователю исправить данные до отправки, но не является защитой. HTML-атрибуты и JavaScript можно изменить или полностью обойти.

Сервер должен заново проверять обязательные поля, формат и длину значений, допустимые диапазоны, права пользователя и правила приложения. Например, только сервер может надёжно определить, свободен ли адрес электронной почты и разрешено ли текущее действие.

Если сервер отклонил данные, интерфейс должен показать понятное сообщение и сохранить введённые значения, чтобы пользователь мог исправить ошибку.

Итого

Стандартные правила задают через required, type, min, max, minlength, maxlength, step и pattern. Браузер проверяет их перед событием submit.

checkValidity() возвращает результат проверки, reportValidity() дополнительно показывает стандартное сообщение, а validity помогает определить причину ошибки. Пользовательские правила задают через setCustomValidity(), обязательно очищая сообщение пустой строкой после исправления значения. Любые данные необходимо повторно проверять на сервере.