Валидация пользовательского ввода

Stimulus — это легковесный JavaScript-фреймворк, предназначенный для улучшения взаимодействия с DOM без необходимости создавать сложную архитектуру SPA. Основная единица организации — контроллер. Контроллер связывает элементы DOM с поведением через атрибуты data-controller, data-action и data-target.

Пример базового контроллера:

// controllers/form_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "error"]

  validate() {
    const value = this.inputTarget.value
    if (value.trim() === "") {
      this.showError("Поле не должно быть пустым")
    } else {
      this.clearError()
    }
  }

  showError(message) {
    this.errorTarget.textContent = message
    this.inputTarget.classList.add("invalid")
  }

  clearError() {
    this.errorTarget.textContent = ""
    this.inputTarget.classList.remove("invalid")
  }
}

В HTML контроллер подключается следующим образом:

<form data-controller="form">
  <input data-form-target="input" data-action="input->form#validate">
  <span data-form-target="error" class="error-message"></span>
</form>

Работа с целевыми элементами и событиями

Targets — это способ явно указать элементы DOM, с которыми работает контроллер. Каждый target объявляется в массиве static targets. Это позволяет избежать поиска элементов через querySelector и поддерживать чистую структуру кода.

Actions связывают события DOM с методами контроллера. Синтаксис event->controller#method позволяет реагировать на любые события, включая input, change, blur, submit и пользовательские события.

Пример множественной валидации:

validateEmail() {
  const email = this.emailTarget.value
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  if (!regex.test(email)) {
    this.showError("Некорректный email")
  } else {
    this.clearError()
  }
}

Использование значений контроллера для конфигурации

Stimulus позволяет задавать динамические значения через static values, что делает контроллеры универсальными.

static values = {
  minLength: Number
}

validateLength() {
  if (this.inputTarget.value.length < this.minLengthValue) {
    this.showError(`Минимальная длина: ${this.minLengthValue}`)
  } else {
    this.clearError()
  }
}

HTML:

<input data-form-target="input" data-form-min-length-value="5" data-action="input->form#validateLength">

Организация комплексной валидации

Для сложных форм с множеством правил рекомендуется разделять методы валидации по типам:

  • Синтаксическая валидация — проверка формата данных (email, телефон, число).
  • Логическая валидация — проверка бизнес-правил (уникальность логина, допустимый диапазон значений).
  • Асинхронная валидация — запрос к серверу для проверки данных.

Асинхронная проверка:

async validateUsername() {
  const username = this.usernameTarget.value
  const response = await fetch(`/api/check_username?username=${username}`)
  const data = await response.json()
  if (!data.available) {
    this.showError("Имя пользователя уже занято")
  } else {
    this.clearError()
  }
}

Состояние элементов и классы CSS

Stimulus упрощает управление визуальной обратной связью через манипуляции с классами и текстом. Основные подходы:

  • add() и remove() для классов ошибки.
  • textContent для отображения сообщений.
  • Комбинирование data-action с CSS-анимациями для плавного появления ошибок.
.invalid {
  border-color: red;
}

.error-message {
  color: red;
  font-size: 0.9em;
  transition: opacity 0.3s;
}

Декларативная валидация через HTML

Stimulus поддерживает декларативный подход, когда логика контроллера определяется через HTML-атрибуты:

<input data-form-target="input" 
       data-form-pattern-value="^\d{10}$" 
       data-action="input->form#validatePattern">

Контроллер:

static values = { pattern: String }

validatePattern() {
  const regex = new RegExp(this.patternValue)
  if (!regex.test(this.inputTarget.value)) {
    this.showError("Неверный формат")
  } else {
    this.clearError()
  }
}

Расширяемость и повторное использование

Контроллеры Stimulus можно наследовать и комбинировать, что позволяет создавать библиотеку общих правил валидации:

import BaseValidationController from "./base_validation_controller"

export default class extends BaseValidationController {
  validatePassword() {
    super.validateMinLength(8)
    super.validateSpecialChars()
  }
}

Совместимость с другими библиотеками

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

Итоговая структура контроллера для форм

Пример комплексного контроллера с несколькими target, значениями и асинхронной проверкой:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "error", "username"]
  static values = { minLength: Number, pattern: String }

  validate() {
    this.validateMinLength()
    this.validatePattern()
    this.validateUsername()
  }

  validateMinLength() {
    if (this.inputTarget.value.length < this.minLengthValue) {
      this.showError(`Минимальная длина: ${this.minLengthValue}`)
    } else {
      this.clearError()
    }
  }

  validatePattern() {
    const regex = new RegExp(this.patternValue)
    if (!regex.test(this.inputTarget.value)) {
      this.showError("Неверный формат")
    }
  }

  async validateUsername() {
    const response = await fetch(`/api/check_username?username=${this.usernameTarget.value}`)
    const data = await response.json()
    if (!data.available) {
      this.showError("Имя пользователя занято")
    }
  }

  showError(message) {
    this.errorTarget.textContent = message
    this.inputTarget.classList.add("invalid")
  }

  clearError() {
    this.errorTarget.textContent = ""
    this.inputTarget.classList.remove("invalid")
  }
}

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