Автосохранение и черновики

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

Контроллеры Stimulus

Основной строительный блок — контроллер. Контроллер связывает HTML-элемент с поведением JavaScript. Для автосохранения ключевым является связывание полей формы с методами, которые реагируют на события ввода.

import { Controller } from "@hotwired/stimulus"

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

  connect() {
    this.loadDraft()
  }

  saveDraft() {
    const data = this.inputTargets.map(target => target.value)
    localStorage.setItem("draft", JSON.stringify(data))
    this.statusTarget.textContent = "Черновик сохранён"
  }

  loadDraft() {
    const data = JSON.parse(localStorage.getItem("draft") || "[]")
    this.inputTargets.forEach((target, index) => {
      target.value = data[index] || ""
    })
  }
}

В этом примере используются targets для полей формы и индикатора состояния. Метод saveDraft сохраняет данные в localStorage, а loadDraft подгружает их при инициализации контроллера.

Определение targets и действий

Targets позволяют обращаться к конкретным элементам внутри контроллера. Для автосохранения полезно создавать targets для каждого поля ввода, а также для элемента, отображающего статус сохранения.

Действия (actions) задаются через атрибуты HTML:

<form data-controller="draft">
  <input data-draft-target="input" data-action="input->draft#saveDraft" type="text" name="title">
  <textarea data-draft-target="input" data-action="input->draft#saveDraft"></textarea>
  <div data-draft-target="status"></div>
</form>

Каждое изменение в input автоматически вызывает метод saveDraft, обеспечивая реактивное автосохранение.

Оптимизация частоты сохранений

Частое обращение к localStorage может быть избыточным. Использование debounce позволяет уменьшить нагрузку:

saveDraft = this.debounce(() => {
  const data = this.inputTargets.map(target => target.value)
  localStorage.setItem("draft", JSON.stringify(data))
  this.statusTarget.textContent = "Черновик сохранён"
}, 500)

debounce(func, wait) {
  let timeout
  return function(...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() => func.apply(this, args), wait)
  }
}

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

Работа с несколькими формами

Stimulus позволяет создавать отдельные контроллеры для разных форм на одной странице, избегая конфликтов данных:

<form data-controller="draft" data-draft-id="post1">
  ...
</form>

<form data-controller="draft" data-draft-id="post2">
  ...
</form>

Контроллер можно модифицировать так, чтобы ключ для localStorage зависел от data-draft-id:

saveDraft() {
  const draftId = this.element.dataset.draftId
  const data = this.inputTargets.map(target => target.value)
  localStorage.setItem(`draft_${draftId}`, JSON.stringify(data))
}

Расширение функционала: автоматическое восстановление

Помимо сохранения, можно реализовать автоматическое восстановление данных при загрузке страницы. Метод connect вызывается автоматически, поэтому подгрузка черновика происходит сразу:

connect() {
  this.loadDraft()
  this.statusTarget.textContent = "Черновик восстановлен"
}

Можно добавить дополнительные проверки, например, удалять черновик после успешной отправки формы:

submitDraft(event) {
  localStorage.removeItem(`draft_${this.element.dataset.draftId}`)
}

Преимущества подхода

  1. Минимальный JavaScript — логика привязана к HTML через контроллеры и actions.
  2. Реактивность — изменения сразу сохраняются, пользователь не теряет данные.
  3. Масштабируемость — отдельные контроллеры для каждой формы.
  4. Поддержка стандартного HTML — не требуется сложная структура компонентов, можно использовать обычные формы и поля.

Stimulus превращает обычные формы в интерактивные элементы с автосохранением, сохраняя простоту и чистоту кода, что особенно важно при реализации черновиков и временного хранения данных.