Проверка формы в браузере
Форма каталога раньше выбирала значения из готовых вариантов. Теперь введём отдельную форму плана чтения: количество часов в неделю и свободную цель. Она должна обнаружить неверные поля, оставить введённое на месте и объяснить, что исправить. Это задача взаимодействия, отличающаяся от проверки схемы загруженного JSON.
Работаем со снимком advanced/lesson-25. Он независим от постраничного API и не отправляет данные серверу. Показанный план существует только в памяти страницы. Пример не запускался; ожидаемые результаты предназначены для ручной сверки. Не принимайте форму за готовую систему регистрации или сохранения персональных сведений.
Нативные ограничения полей
Поле часов использует type="number", required, минимум один, максимум сорок и шаг один. Эти атрибуты задают понятный начальный договор. Количество часов должно быть целым и находиться в допустимом диапазоне. Строка цели обязательна и имеет верхнее ограничение длины.
<form id="plan-form" novalidate>
<label>Часов в неделю
<input name="hours" type="number" min="1" max="40" step="1" required>
</label>
<label>Цель
<textarea name="goal" required maxlength="120" aria-describedby="goal-hint"></textarea>
</label>
<p id="goal-hint">От 10 до 120 символов после удаления пробелов по краям.</p>
<button type="submit">Составить план</button>
</form>
<p id="demo-status" role="status"></p>
В снимке эта разметка находится внутри полного документа. novalidate отключает автоматическую проверку при отправке, потому что обработчик сначала задаёт собственное условие цели, затем явно вызывает reportValidity. Он не удаляет ограничения полей. Если забыть ручной вызов, форма действительно примет неверное значение, поэтому эти части договора связаны.
Нативный механизм проверяет обязательность, диапазон и шаг, но не знает наш смысл цели обучения. Строка из пробелов технически не равна пустой строке. Потребуется отдельное условие после удаления пробелов по краям. Не пытайтесь описать любую предметную проверку сложным HTML-шаблоном, если её проще явно объяснить в функции.
Основные ограничения и способы запуска проверки рассматриваются в руководстве Constraint Validation. Для числа можно читать valueAsNumber, который возвращает числовой результат или NaN при отсутствии допустимого числа; поведение описано в MDN.
Собственное правило цели
Программа получает ссылки на поля через форму. Функция проверки устанавливает специальное сообщение или снимает его:
function validateGoal() {
const length = goal.value.trim().length;
goal.setCustomValidity(length >= 10 && length <= 120
? '' : 'Опишите цель: от 10 до 120 символов');
}
Здесь length измеряет кодовые единицы строки JavaScript. Для обычного русского текста это часто похоже на число букв, но некоторые emoji и комбинируемые знаки ведут себя иначе. Подробное различие изучим в уроке Unicode. У текущей формы именно такой технический предел; нельзя обещать полноценный подсчёт воспринимаемых человеком символов без другой операции.
Непустое сообщение setCustomValidity сохраняет поле в невалидном состоянии. Оно не исчезает автоматически лишь потому, что пользователь начал исправление. В снимке обработчик input очищает сообщение, а при следующем применении полное правило вычисляется заново. Так устаревший отказ не блокирует уже исправленную цель.
Очистка сообщения не означает, что новое значение принято. До следующей проверки оно только редактируется. Это полезное различие между текущим вводом и готовым планом. Если продукт хочет показывать состояние каждого символа немедленно, потребуется другая политика частоты проверки и уведомлений.
Отказ без потери ввода
Полный обработчик формы выглядит так:
form.addEventListener('submit', event => {
event.preventDefault();
validateGoal();
if (!form.reportValidity()) {
status.textContent = 'Исправьте отмеченное поле; ваши значения сохранены.';
return;
}
const plan = {
hoursPerWeek: hours.valueAsNumber,
goal: goal.value.trim(),
};
status.textContent = `План подготовлен: ${plan.hoursPerWeek} ч. в неделю.`;
console.log(plan);
});
reportValidity запускает проверку и просит браузер сообщить о неверном поле. Точное оформление встроенного сообщения зависит от браузера и языка интерфейса. Наша дополнительная строка объясняет общую обязанность: исправить ввод, сохранив заполненное. Не утверждаем одинаковую визуальную подсказку для всех сред без ручного просмотра.
При отказе не вызывается reset и не заменяется HTML формы. Читатель сохраняет свою цель и исправляет только нужный участок. Это существенное поведение: успешное обнаружение ошибки бесполезно, если форма стирает длинный текст и заставляет начинать заново.
После успешной проверки создаётся отдельный объект плана. Его числовое поле не содержит оформленную строку «три часа», а цель очищена по договору. Текст сообщения и объект результата выполняют разные обязанности. Не считывайте часы обратно из подписи статуса при дальнейшей обработке.
Где проходит граница доверия
Проверка браузера помогает пользователю, но не защищает серверную операцию сама по себе. Запрос можно отправить другим способом, а атрибуты DOM изменить. Когда форма начнёт сохранять план, сервер должен самостоятельно проверить типы, диапазон и права операции. Наш снимок не содержит такого endpoint и не изображает выполненное сохранение.
Также не храните ввод в localStorage автоматически только потому, что следующий урок рассматривает настройки. Цель может быть личной, а привычное поле предпочтений — другое содержимое. Пользователь должен понимать, что сохраняется, где и как удаляется. Пока форма не делает постоянного хранения вообще.
Общий статус не заменяет подписи и связи конкретных полей. label объясняет назначение, aria-describedby связывает цель с подсказкой. Для полноценной доступности нужны клавиатурная проверка и наблюдение вспомогательной программой. Наличие атрибута не является доказательством готовности всех сценариев.
Для ручного опыта отправьте пустую форму, затем часы ноль, дробное количество и короткую цель. Ожидается отказ без стирания. После корректных часов и подробной цели ожидается объект в консоли и сообщение о подготовленном плане. Число сорок один должно нарушить верхнюю границу, сорок — соответствовать ей.
Черновик поля и нормализованный план
Введённая строка и полученный объект плана могут различаться без потери пользовательского текста. Если у цели есть пробелы по краям, программа убирает их в создаваемом объекте, но не переписывает значение textarea. Поэтому после успеха человек продолжает видеть свой ввод, а дальнейшая обработка получает нормализованную цель. Это удобная граница: отображение редактируемого черновика не обязано совпадать побуквенно с данными принятого результата.
Верхняя граница имеет две ступени. maxlength ограничивает исходный ввод поля по правилам HTML, а собственная функция оценивает длину после trim. Строка с большим количеством краевых пробелов может упереться в нативный предел раньше, чем станет длинной содержательно. Наш снимок принимает это правило. Если редактор должен разрешать произвольный черновик, ограничивая только итоговую цель, понадобится другой договор поля; не убирайте атрибут, не объяснив последствия.
Количество часов читается только после успешной проверки всей формы. Простое преобразование через Number до неё создало бы двусмысленность пустой строки. Здесь valueAsNumber выражает назначение числового поля, а ограничения диапазона и шага гарантируют предметную допустимость перед созданием плана. Даже после этого объект не следует считать проверенным сервером: он ещё не пересекал границу настоящего сохранения.
Для последовательного наблюдения заполните подробную цель и неверное количество часов. Исправьте только число и отправьте повторно. Ожидается сохранение прежней цели и получение одного нового объекта. Затем сократите цель до нескольких букв, получите отказ и снова допишите текст. Старое сообщение должно очиститься на вводе, а новая отправка заново решить, достаточно ли содержимого.
Не вызывайте reset после успешной подготовки лишь ради красивого пустого экрана. Пользователь может захотеть изменить часы или скопировать цель. Очистка является отдельным действием интерфейса и должна соответствовать задаче. Наш пример показывает подготовленный план, сохраняет поля и не создаёт впечатление, что их содержимое было куда-то отправлено.
Наконец, сохраните различие между отказом ввода и ошибкой реализации. Если программа не нашла поле из-за переименования name, это не повод показывать пользователю, что его цель слишком короткая. Следующий урок вернётся к карточкам: добавим действие для динамически создаваемых кнопок, сохранив один обработчик на общем списке.