Валидация на клиентской стороне

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

Валидация реализуется через контроллеры, которые связываются с формами и их полями с помощью data-атрибутов. Основная идея заключается в том, что каждый элемент формы может быть “снабжен” контроллером, который обрабатывает события input, change или submit.


Создание контроллера для валидации

Контроллер создаётся командой:

rails generate stimulus form_validation

или вручную создается файл form_validation_controller.js:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["email", "password", "submit"]

  connect() {
    this.submitTarget.disabled = true
  }

  validate() {
    const emailValid = this.validateEmail(this.emailTarget.value)
    const passwordValid = this.passwordTarget.value.length >= 8

    if (emailValid && passwordValid) {
      this.submitTarget.disabled = false
    } else {
      this.submitTarget.disabled = true
    }
  }

  validateEmail(email) {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return re.test(email)
  }
}

В данном примере:

  • Targets: используются для указания элементов формы, за которыми контроллер следит.
  • connect(): вызывается при инициализации контроллера и позволяет задать начальное состояние элементов.
  • validate(): основной метод, который проверяет введённые данные.
  • validateEmail(): вспомогательный метод для проверки формата email.

Привязка контроллера к HTML

HTML-разметка формы связывается с контроллером через атрибуты data-controller и data-target:

<form data-controller="form-validation" data-action="input->form-validation#validate">
  <label>Email</label>
  <input type="email" data-form-validation-target="email">

  <label>Пароль</label>
  <input type="password" data-form-validation-target="password">

  <button type="submit" data-form-validation-target="submit">Отправить</button>
</form>
  • data-controller связывает форму с контроллером.
  • data-target определяет элементы, за которыми следит контроллер.
  • data-action указывает, что при событии input вызывается метод validate.

Работа с динамическими ошибками

Для улучшения UX часто необходимо показывать сообщения об ошибках рядом с полями. В Stimulus это делается через отдельные targets для сообщений об ошибках:

static targets = ["email", "password", "submit", "emailError", "passwordError"]

validate() {
  let valid = true

  if (!this.validateEmail(this.emailTarget.value)) {
    this.emailErrorTarget.textContent = "Некорректный email"
    valid = false
  } else {
    this.emailErrorTarget.textContent = ""
  }

  if (this.passwordTarget.value.length < 8) {
    this.passwordErrorTarget.textContent = "Пароль должен быть не менее 8 символов"
    valid = false
  } else {
    this.passwordErrorTarget.textContent = ""
  }

  this.submitTarget.disabled = !valid
}

HTML для сообщений:

<div>
  <input type="email" data-form-validation-target="email">
  <span data-form-validation-target="emailError" class="error"></span>
</div>

<div>
  <input type="password" data-form-validation-target="password">
  <span data-form-validation-target="passwordError" class="error"></span>
</div>

Такой подход позволяет поддерживать чистую логику валидации, не смешивая её с HTML-разметкой напрямую.


Расширяемость и модульность

Stimulus контроллеры легко комбинируются и расширяются. Например, можно вынести повторяющиеся проверки в отдельный validation.js:

export function isEmail(value) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return re.test(value)
}

export function minLength(value, length) {
  return value.length >= length
}

И использовать их в контроллере:

import { Controller } from "@hotwired/stimulus"
import { isEmail, minLength } from "../utils/validation"

export default class extends Controller {
  static targets = ["email", "password", "submit", "emailError", "passwordError"]

  validate() {
    let valid = true

    if (!isEmail(this.emailTarget.value)) {
      this.emailErrorTarget.textContent = "Некорректный email"
      valid = false
    } else {
      this.emailErrorTarget.textContent = ""
    }

    if (!minLength(this.passwordTarget.value, 8)) {
      this.passwordErrorTarget.textContent = "Пароль должен быть не менее 8 символов"
      valid = false
    } else {
      this.passwordErrorTarget.textContent = ""
    }

    this.submitTarget.disabled = !valid
  }
}

Это позволяет легко добавлять новые правила валидации и переиспользовать их в разных контроллерах.


Валидация при сабмите формы

Помимо проверки во время ввода, можно подключать проверку при отправке формы:

submitForm(event) {
  if (!this.validate()) {
    event.preventDefault()
  }
}

HTML:

<form data-controller="form-validation" data-action="submit->form-validation#submitForm">

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


Реактивная валидация с множественными полями

Stimulus хорошо работает с формами, где зависимости между полями важны. Например, подтверждение пароля:

static targets = ["password", "passwordConfirm", "passwordError", "passwordConfirmError"]

validate() {
  let valid = true

  if (this.passwordTarget.value !== this.passwordConfirmTarget.value) {
    this.passwordConfirmErrorTarget.textContent = "Пароли не совпадают"
    valid = false
  } else {
    this.passwordConfirmErrorTarget.textContent = ""
  }

  this.submitTarget.disabled = !valid
}

Подобная структура позволяет легко добавлять новые проверки без переписывания основной логики.


Stimulus обеспечивает структурированный, модульный и реактивный подход к клиентской валидации, позволяя писать чистый и поддерживаемый код без громоздких библиотек. Ключевыми преимуществами являются targets для отслеживания элементов, actions для событий и modularity для переиспользования правил.