Урок 06

Формы и события форм

События форм

  • submit — отправка формы
  • reset — сброс
  • input — ввод в поле (мгновенно)
  • change — изменение (после потери фокуса)
  • focus, blur — фокус
  • invalid — ошибка валидации

Submit

const form = document.querySelector('form');

form.addEventListener('submit', (event) => {
    event.preventDefault();

    const formData = new FormData(form);
    const data = Object.fromEntries(formData);

    console.log(data);
});

preventDefault обязателен — иначе страница перезагрузится.

FormData

const formData = new FormData(form);

formData.get('email');        // значение
formData.getAll('tags');      // все значения
formData.has('email');        // true
formData.append('extra', '1');
formData.set('email', 'new@example.com');
formData.delete('extra');

Преобразование в объект:

const data = Object.fromEntries(formData);

Input vs change

input.addEventListener('input', () => {
    console.log('на каждый символ');
});

input.addEventListener('change', () => {
    console.log('после потери фокуса');
});

input — мгновенно, change — после завершения ввода.

Валидация

form.addEventListener('submit', (event) => {
    if (!form.checkValidity()) {
        event.preventDefault();
        // показать ошибки
    }
});

Встроенные атрибуты:

<input type="email" required>
<input type="text" minlength="3" maxlength="20">
<input type="number" min="0" max="100">
<input type="text" pattern="[A-Za-z]+">

Валидация через JS

input.addEventListener('input', () => {
    if (input.validity.valid) {
        input.classList.remove('invalid');
    } else {
        input.classList.add('invalid');
    }
});

Свойства validity:

  • valueMissing — не заполнено
  • typeMismatch — неверный тип
  • patternMismatch — не соответствует pattern
  • tooShort, tooLong
  • rangeUnderflow, rangeOverflow
  • valid — всё ок

Кастомные сообщения

input.addEventListener('invalid', (event) => {
    event.target.setCustomValidity('Введите корректный email');
});

input.addEventListener('input', (event) => {
    event.target.setCustomValidity('');
});

Checkbox

checkbox.addEventListener('change', () => {
    console.log(checkbox.checked); // true/false
});

Radio

document.querySelectorAll('input[name="gender"]').forEach((radio) => {
    radio.addEventListener('change', () => {
        if (radio.checked) console.log(radio.value);
    });
});

Или через форму:

form.addEventListener('change', (event) => {
    if (event.target.name === 'gender') {
        console.log(event.target.value);
    }
});

Select

select.addEventListener('change', () => {
    console.log(select.value);
    console.log(select.selectedOptions); // выбранные option
});

Множественный:

[...select.selectedOptions].map((o) => o.value);

Файлы

fileInput.addEventListener('change', () => {
    const files = fileInput.files;

    for (const file of files) {
        console.log(file.name, file.size, file.type);
    }
});

Отправка через fetch

form.addEventListener('submit', async (event) => {
    event.preventDefault();

    const formData = new FormData(form);

    try {
        const res = await fetch('/api/submit', {
            method: 'POST',
            body: formData,
        });

        if (!res.ok) throw new Error(`HTTP ${res.status}`);

        form.reset();
        showSuccess();
    } catch (error) {
        showError(error.message);
    }
});

Не устанавливай Content-Type для FormData — браузер сам.

JSON

const data = Object.fromEntries(new FormData(form));

await fetch('/api/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
});

Сброс

form.reset(); // к значениям по умолчанию

Итоги

  • submit + preventDefault
  • FormData для сбора
  • input мгновенно, change после
  • Валидация через checkValidity или атрибуты
  • validity для детальной проверки
  • Отправка через fetch с FormData или JSON