← JavaScript/Формы и события ввода#107 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Формы и события ввода

Форма регистрации на GitHub проверяет доступность никнейма в реальном времени — пока ты печатаешь. Форма оплаты на Ozon мгновенно показывает ошибку если ввести неверный CVV. Форма поиска в Avito фильтрует результаты при каждом символе. Всё это — обработка событий форм через JavaScript.

Какую проблему решает

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

На основе предыдущих уроков

  • События — addEventListener, preventDefault
  • DOM — querySelector, получение элементов
  • Fetch — отправка данных на сервер
  • RegExp — валидация через регулярные выражения
  • События форм

    // input — срабатывает при КАЖДОМ изменении (каждый символ)
    input.addEventListener('input', (e) => {
      console.log(e.target.value)  // текущее значение
      validateField(e.target)
    })
    
    // change — срабатывает при потере фокуса или выборе в select
    input.addEventListener('change', handler)
    
    // focus / blur — получение/потеря фокуса
    input.addEventListener('focus', () => input.classList.add('focused'))
    input.addEventListener('blur',  () => validateOnBlur(input))
    
    // submit — отправка формы
    form.addEventListener('submit', (e) => {
      e.preventDefault()  // ОБЯЗАТЕЛЬНО — отменяет перезагрузку страницы
      handleSubmit(e)
    })

    Значения полей

    input.value             // текст в поле
    checkbox.checked        // boolean для чекбоксов
    select.value            // выбранный вариант
    textarea.value          // текст в textarea

    FormData — сбор всех данных формы

    form.addEventListener('submit', (e) => {
      e.preventDefault()
      const data = new FormData(form)
    
      data.get('email')     // значение поля с name="email"
      data.getAll('tags')   // массив для multiple-select
    
      // Преобразовать в обычный объект
      const obj = Object.fromEntries(data)
    })

    Constraint Validation API

    input.validity.valid         // true/false
    input.validity.valueMissing  // required поле пустое
    input.validity.typeMismatch  // неправильный тип (email, url)
    input.validity.tooShort      // короче minlength
    
    input.setCustomValidity('Логин уже занят')  // установить ошибку
    input.setCustomValidity('')                 // сбросить ошибку
    input.checkValidity()                       // true/false

    Паттерн валидации с отображением ошибок

    function validateEmail(value) {
      if (!value.trim()) return 'Email обязателен'
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Некорректный email'
      return null  // нет ошибки
    }
    
    emailInput.addEventListener('blur', () => {
      const error = validateEmail(emailInput.value)
      if (error) showFieldError(emailInput, error)
      else clearFieldError(emailInput)
    })

    Типичные ошибки

    Ошибка 1: забыли e.preventDefault() — страница перезагружается

    // Нет e.preventDefault() — страница перезагрузится!
    form.addEventListener('submit', (e) => {
      sendData(new FormData(form))
    })
    
    // Правильно
    form.addEventListener('submit', (e) => {
      e.preventDefault()  // всегда первым
      sendData(new FormData(form))
    })

    Ошибка 2: валидация только на submit — плохой UX

    // Пользователь узнаёт об ошибке только после нажатия кнопки
    form.addEventListener('submit', validate)
    
    // Лучше — валидируем при потере фокуса (blur)
    inputs.forEach(input => {
      input.addEventListener('blur', () => validateField(input))
    })

    Ошибка 3: data.get() возвращает строку

    const data = new FormData(form)
    const age = data.get('age')  // '25' — строка, не число!
    
    // Правильно
    const age = Number(data.get('age'))
    const price = parseFloat(data.get('price'))

    В реальных проектах

  • Регистрация: проверка доступности никнейма через API при вводе
  • Оплата: форматирование номера карты 4-4-4-4 при вводе
  • Поиск: debounce на input для уменьшения запросов к API
  • Загрузка файлов: FormData с файлом через input[type=file]
  • Примеры

    Симуляция обработки и валидации формы регистрации

    // Симуляция FormData через объект
    function mockFormData(fields) {
      return {
        data: Object.assign({}, fields),
        get(name) { return this.data[name] ?? null },
        has(name) { return name in this.data },
        set(name, value) { this.data[name] = value },
        entries() { return Object.entries(this.data) }
      }
    }
    
    // Валидация формы регистрации (GitHub-подобный сервис)
    function validateRegistration(formData) {
      const errors = {}
    
      const username = (formData.get('username') || '').trim()
      const email    = (formData.get('email') || '').trim()
      const password = formData.get('password') || ''
    
      if (!username) {
        errors.username = 'Имя пользователя обязательно'
      } else if (username.length < 3) {
        errors.username = 'Минимум 3 символа'
      } else if (!/^[a-zA-Z0-9-]+$/.test(username)) {
        errors.username = 'Только латиница, цифры и дефис'
      }
    
      if (!email) {
        errors.email = 'Email обязателен'
      } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        errors.email = 'Некорректный формат email'
      }
    
      if (!password) {
        errors.password = 'Пароль обязателен'
      } else if (password.length < 8) {
        errors.password = 'Минимум 8 символов'
      } else if (!/\d/.test(password)) {
        errors.password = 'Пароль должен содержать хотя бы одну цифру'
      }
    
      return { valid: Object.keys(errors).length === 0, errors }
    }
    
    // Тест 1: невалидные данные
    const badData = mockFormData({ username: 'ab', email: 'не-email', password: 'abc' })
    const r1 = validateRegistration(badData)
    console.log(r1.valid)                    // false
    console.log(Object.keys(r1.errors))      // ['username', 'email', 'password']
    
    // Тест 2: валидные данные
    const goodData = mockFormData({ username: 'alice-dev', email: 'alice@github.com', password: 'secure123' })
    const r2 = validateRegistration(goodData)
    console.log(r2.valid)    // true
    console.log(r2.errors)   // {}
    
    // Тест 3: частично верные данные
    const partialData = mockFormData({ username: 'bob', email: 'bob@mail.ru', password: 'nodigits' })
    const r3 = validateRegistration(partialData)
    console.log(r3.valid)              // false
    console.log(r3.errors.password)   // 'Пароль должен содержать хотя бы одну цифру'

    Формы и события ввода

    Форма регистрации на GitHub проверяет доступность никнейма в реальном времени — пока ты печатаешь. Форма оплаты на Ozon мгновенно показывает ошибку если ввести неверный CVV. Форма поиска в Avito фильтрует результаты при каждом символе. Всё это — обработка событий форм через JavaScript.

    Какую проблему решает

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

    На основе предыдущих уроков

  • События — addEventListener, preventDefault
  • DOM — querySelector, получение элементов
  • Fetch — отправка данных на сервер
  • RegExp — валидация через регулярные выражения
  • События форм

    // input — срабатывает при КАЖДОМ изменении (каждый символ)
    input.addEventListener('input', (e) => {
      console.log(e.target.value)  // текущее значение
      validateField(e.target)
    })
    
    // change — срабатывает при потере фокуса или выборе в select
    input.addEventListener('change', handler)
    
    // focus / blur — получение/потеря фокуса
    input.addEventListener('focus', () => input.classList.add('focused'))
    input.addEventListener('blur',  () => validateOnBlur(input))
    
    // submit — отправка формы
    form.addEventListener('submit', (e) => {
      e.preventDefault()  // ОБЯЗАТЕЛЬНО — отменяет перезагрузку страницы
      handleSubmit(e)
    })

    Значения полей

    input.value             // текст в поле
    checkbox.checked        // boolean для чекбоксов
    select.value            // выбранный вариант
    textarea.value          // текст в textarea

    FormData — сбор всех данных формы

    form.addEventListener('submit', (e) => {
      e.preventDefault()
      const data = new FormData(form)
    
      data.get('email')     // значение поля с name="email"
      data.getAll('tags')   // массив для multiple-select
    
      // Преобразовать в обычный объект
      const obj = Object.fromEntries(data)
    })

    Constraint Validation API

    input.validity.valid         // true/false
    input.validity.valueMissing  // required поле пустое
    input.validity.typeMismatch  // неправильный тип (email, url)
    input.validity.tooShort      // короче minlength
    
    input.setCustomValidity('Логин уже занят')  // установить ошибку
    input.setCustomValidity('')                 // сбросить ошибку
    input.checkValidity()                       // true/false

    Паттерн валидации с отображением ошибок

    function validateEmail(value) {
      if (!value.trim()) return 'Email обязателен'
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Некорректный email'
      return null  // нет ошибки
    }
    
    emailInput.addEventListener('blur', () => {
      const error = validateEmail(emailInput.value)
      if (error) showFieldError(emailInput, error)
      else clearFieldError(emailInput)
    })

    Типичные ошибки

    Ошибка 1: забыли e.preventDefault() — страница перезагружается

    // Нет e.preventDefault() — страница перезагрузится!
    form.addEventListener('submit', (e) => {
      sendData(new FormData(form))
    })
    
    // Правильно
    form.addEventListener('submit', (e) => {
      e.preventDefault()  // всегда первым
      sendData(new FormData(form))
    })

    Ошибка 2: валидация только на submit — плохой UX

    // Пользователь узнаёт об ошибке только после нажатия кнопки
    form.addEventListener('submit', validate)
    
    // Лучше — валидируем при потере фокуса (blur)
    inputs.forEach(input => {
      input.addEventListener('blur', () => validateField(input))
    })

    Ошибка 3: data.get() возвращает строку

    const data = new FormData(form)
    const age = data.get('age')  // '25' — строка, не число!
    
    // Правильно
    const age = Number(data.get('age'))
    const price = parseFloat(data.get('price'))

    В реальных проектах

  • Регистрация: проверка доступности никнейма через API при вводе
  • Оплата: форматирование номера карты 4-4-4-4 при вводе
  • Поиск: debounce на input для уменьшения запросов к API
  • Загрузка файлов: FormData с файлом через input[type=file]
  • Примеры

    Симуляция обработки и валидации формы регистрации

    // Симуляция FormData через объект
    function mockFormData(fields) {
      return {
        data: Object.assign({}, fields),
        get(name) { return this.data[name] ?? null },
        has(name) { return name in this.data },
        set(name, value) { this.data[name] = value },
        entries() { return Object.entries(this.data) }
      }
    }
    
    // Валидация формы регистрации (GitHub-подобный сервис)
    function validateRegistration(formData) {
      const errors = {}
    
      const username = (formData.get('username') || '').trim()
      const email    = (formData.get('email') || '').trim()
      const password = formData.get('password') || ''
    
      if (!username) {
        errors.username = 'Имя пользователя обязательно'
      } else if (username.length < 3) {
        errors.username = 'Минимум 3 символа'
      } else if (!/^[a-zA-Z0-9-]+$/.test(username)) {
        errors.username = 'Только латиница, цифры и дефис'
      }
    
      if (!email) {
        errors.email = 'Email обязателен'
      } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        errors.email = 'Некорректный формат email'
      }
    
      if (!password) {
        errors.password = 'Пароль обязателен'
      } else if (password.length < 8) {
        errors.password = 'Минимум 8 символов'
      } else if (!/\d/.test(password)) {
        errors.password = 'Пароль должен содержать хотя бы одну цифру'
      }
    
      return { valid: Object.keys(errors).length === 0, errors }
    }
    
    // Тест 1: невалидные данные
    const badData = mockFormData({ username: 'ab', email: 'не-email', password: 'abc' })
    const r1 = validateRegistration(badData)
    console.log(r1.valid)                    // false
    console.log(Object.keys(r1.errors))      // ['username', 'email', 'password']
    
    // Тест 2: валидные данные
    const goodData = mockFormData({ username: 'alice-dev', email: 'alice@github.com', password: 'secure123' })
    const r2 = validateRegistration(goodData)
    console.log(r2.valid)    // true
    console.log(r2.errors)   // {}
    
    // Тест 3: частично верные данные
    const partialData = mockFormData({ username: 'bob', email: 'bob@mail.ru', password: 'nodigits' })
    const r3 = validateRegistration(partialData)
    console.log(r3.valid)              // false
    console.log(r3.errors.password)   // 'Пароль должен содержать хотя бы одну цифру'

    Задание

    Напиши функцию validateForm(data) для формы настроек профиля. Принимает объект { name, email, password }. Правила: name — минимум 2 символа; email — содержит @ и точку после @; password — минимум 8 символов, хотя бы одна цифра И хотя бы одна заглавная буква. Возвращает { valid: boolean, errors: Record<string, string> }.

    Подсказка

    Email: !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(data.email). Заглавная буква в пароле: !/[A-Z]/.test(data.password). Проверь порядок условий: сначала длина, потом цифра, потом заглавная.

    Загружаем среду выполнения...
    Загружаем AI-помощника...