Формы и валидация

Формы - это мост между пользователем и сервером. Пользователь вводит данные, браузер отправляет их на сервер, сервер обрабатывает (на стороне PHP - см. уроки про формы).

Базовая форма

<form method="post" action="/signup">
  <label for="email">Почта</label>
  <input id="email" name="email" type="email" required />

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

  <button type="submit">Регистрация</button>
</form>

method - HTTP-метод (get или post). action - URL, куда отправить данные. name - ключ, под которым данные придут на сервер.

GET vs POST

GET - данные в URL:

/search?query=flexbox&lang=ru

Используй для поиска, фильтров, пагинации. Данные видны в адресной строке, сохраняются в истории, можно поделиться ссылкой.

POST - данные в теле запроса:

POST /signup HTTP/1.1
Content-Type: application/x-www-form-urlencoded

email=ivan%40mail.ru&password=12345678

Используй для паролей, персональных данных, создания записей. Данные не видны в URL, не сохраняются в истории.

POST скрывает данные из URL, но не шифрует их. Без HTTPS всё передаётся открытым текстом. Для конфиденциальных данных нужен HTTPS.

Типы полей ввода

<!-- Текст -->
<input type="text" />           <!-- обычный текст -->
<input type="email" />          <!-- email (браузер проверит @) -->
<input type="password" />       <!-- пароль (скрыт точками) -->
<input type="url" />            <!-- URL -->
<input type="tel" />            <!-- телефон (мобильная клавиатура с цифрами) -->
<input type="number" />         <!-- число (стрелки + / -) -->
<input type="search" />         <!-- поиск (крестик для очистки) -->

<!-- Дата/время -->
<input type="date" />           <!-- календарь -->
<input type="time" />           <!-- часы -->
<input type="datetime-local" /> <!-- дата + время -->

<!-- Выбор -->
<input type="checkbox" />       <!-- галочка -->
<input type="radio" />          <!-- один из группы -->
<input type="range" min="0" max="100" /> <!-- слайдер -->
<input type="color" />          <!-- выбор цвета -->
<input type="file" />           <!-- загрузка файла -->

<!-- Скрытое -->
<input type="hidden" name="csrf" value="abc123" />

type определяет не только вид поля, но и клавиатуру на мобилке: type="email" покажет клавиатуру с @, type="tel" - цифровую.

Элементы формы

textarea - многострочный текст

<textarea name="message" rows="5" placeholder="Ваше сообщение..."></textarea>

select - выпадающий список

<select name="role">
  <option value="">Выберите роль</option>
  <option value="backend">Backend</option>
  <option value="frontend">Frontend</option>
  <option value="fullstack">Fullstack</option>
</select>

fieldset + legend - группировка

<fieldset>
  <legend>Контактная информация</legend>
  <label>Имя <input name="name" required /></label>
  <label>Email <input name="email" type="email" required /></label>
</fieldset>

HTML-валидация

Браузер может проверять данные до отправки:

<!-- Обязательное поле -->
<input required />

<!-- Минимальная/максимальная длина -->
<input minlength="8" maxlength="100" />

<!-- Числовой диапазон -->
<input type="number" min="1" max="120" />

<!-- Регулярное выражение -->
<input pattern="[0-9]{10}" title="Введите 10 цифр" />

<!-- Email - встроенная проверка -->
<input type="email" />

Браузер покажет всплывающую подсказку, если данные не проходят валидацию.

HTML-валидация - для удобства пользователя. Она легко обходится: откроешь DevTools, удалишь атрибут `required` - и отправишь пустую форму. Сервер ОБЯЗАН валидировать всё сам.

Стилизация валидации

CSS-псевдоклассы для стилей валидации:

/* Валидное поле */
input:valid {
  border-color: #38a169;
}

/* Невалидное поле (после попытки отправки) */
input:invalid {
  border-color: #e53e3e;
}

/* Невалидное и пользователь уже взаимодействовал */
input:invalid:not(:placeholder-shown) {
  border-color: #e53e3e;
  background: #fff5f5;
}

/* Поле в фокусе */
input:focus {
  outline: 2px solid #3182ce;
  outline-offset: 2px;
}

:placeholder-shown - хитрость, чтобы не показывать красную рамку сразу при загрузке (пока пользователь не начал заполнять).

Отправка формы через JavaScript

<form id="contactForm">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Отправить</button>
</form>
const form = document.getElementById('contactForm');

form.addEventListener('submit', async (e) => {
  e.preventDefault(); // не перезагружать страницу

  const data = new FormData(form);

  const response = await fetch('/api/contact', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(Object.fromEntries(data))
  });

  if (response.ok) {
    form.reset();
    alert('Сообщение отправлено!');
  } else {
    alert('Ошибка отправки');
  }
});

FormData собирает все поля формы автоматически. Object.fromEntries() превращает FormData в обычный объект для JSON.

`new FormData(form)` соберёт все поля с атрибутом `name` автоматически. Не нужно читать каждое поле отдельно через `document.getElementById`.

Загрузка файлов

<form enctype="multipart/form-data" method="post" action="/upload">
  <input type="file" name="avatar" accept="image/*" />
  <button type="submit">Загрузить</button>
</form>

enctype="multipart/form-data" - обязательно для файлов. Без него файл не отправится. accept ограничивает типы файлов в диалоге выбора (но сервер всё равно должен проверять).

Доступность форм

<!-- Всегда связывай label с input -->
<label for="name">Имя</label>
<input id="name" name="name" />

<!-- Или оборачивай input в label -->
<label>
  Email
  <input name="email" type="email" />
</label>

<!-- aria-describedby для подсказок -->
<input id="pass" aria-describedby="pass-hint" />
<small id="pass-hint">Минимум 8 символов, одна цифра</small>

<!-- Кнопка submit, не div с onclick -->
<button type="submit">Отправить</button>  <!-- доступна с клавиатуры -->
<div onclick="submit()">Отправить</div>   <!-- не доступна -->
Без связки label/for: скринридеры не озвучат, клик по тексту не поставит фокус. На мобилке - область нажатия минимальная. Всегда связывай.

Мини-задание

  • Сделай форму «обратная связь» с полями: имя, email, сообщение (textarea), тема (select)
  • Добавь required, minlength, type="email"
  • Добавь стилизацию валидации через :valid / :invalid
  • Перехвати submit через JavaScript и отправь данные через fetch()
  • Посмотри в DevTools → Network как выглядит отправленный запрос

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.