form-auto-save

📁 rolemodel/rolemodel-skills 📅 7 days ago
9
总安装量
9
周安装量
#32639
全站排名
安装命令
npx skills add https://github.com/rolemodel/rolemodel-skills --skill form-auto-save

Agent 安装分布

github-copilot 9
opencode 8
gemini-cli 8
codex 8
kimi-cli 8
amp 8

Skill 文档

Form Auto Save Skill

Overview

The Form Auto Save pattern provides automatic form submission after user input changes, using a debounce mechanism to prevent excessive server requests. This creates a seamless “auto-save” experience for users editing forms.

When to Use

  • Long-form editing interfaces where users expect automatic saving
  • Forms with rich text editors or multiple fields
  • Edit pages where users might navigate away and expect changes to persist
  • Forms that benefit from progressive saving without explicit “Save” button clicks

Implementation

1. Stimulus Controller

The pattern uses a Stimulus controller (form-auto-save) that handles the auto-save logic.

Controller Location: app/javascript/controllers/form_auto_save_controller.js

Key Features:

  • Debounce time of 8 seconds (configurable via static DEBOUNCE_TIME)
  • Listens to both change and lexxy:change events (for custom components)
  • Uses passive event listeners for better performance
  • Provides cancel() and submit() methods for programmatic control

Controller Code Pattern:

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static DEBOUNCE_TIME = 8000

  connect() {
    this.element.addEventListener('change', this.#debounceSubmit.bind(this), { passive: true })
    this.element.addEventListener('lexxy:change', this.#debounceSubmit.bind(this), { passive: true })
  }

  cancel() {
    clearTimeout(this.debounceTimer)
  }

  submit() {
    this.element.requestSubmit()
  }

  #debounceSubmit() {
    this.#debounce(this.submit.bind(this))
  }

  #debounce(callback) {
    clearTimeout(this.debounceTimer)
    this.debounceTimer = setTimeout(callback, this.constructor.DEBOUNCE_TIME)
  }
}

2. View Integration

Attach the controller to the form element using Stimulus data attributes.

Required Attributes:

  • data: { controller: 'form-auto-save' } – Attaches the Stimulus controller
  • data: { turbo_permanent: true } – Optional but recommended to preserve form state during Turbo navigation

Example (Slim):

= simple_form_for resource, html: { data: { controller: 'form-auto-save', turbo_permanent: true } } do |f|
  = f.input :field_name
  = f.rich_text_area :content

Important Considerations

Debounce Time

  • Default: 8 seconds (8000ms)
  • Adjust via static DEBOUNCE_TIME in the controller if needed
  • Consider user experience: too short = excessive requests, too long = lost changes

Event Listeners

  • Listens to change events (standard HTML input changes)
  • Listens to lexxy:change events (custom component events, like rich text editors)
  • Uses passive listeners for better scroll performance

Turbo Permanent

  • turbo_permanent: true keeps the form element across Turbo navigation
  • Prevents loss of unsaved changes when user navigates
  • Critical for forms with auto-save to maintain debounce timers

Form Validation

  • Ensure backend validation handles partial saves gracefully
  • Consider whether all fields should be required or allow partial completion
  • Provide clear error feedback if auto-save fails

Testing

For testing auto-save functionality, use the turbo-fetch controller alongside form-auto-save to track request completion without relying on sleep timers.

Turbo Fetch Controller

Add this controller to your JavaScript controllers:

File: app/javascript/controllers/turbo_fetch_controller.js

import { Controller } from '@hotwired/stimulus'
import { patch } from '@rails/request.js'

export default class extends Controller {
  static values = {
    url: String,
    count: Number,
    isRunning: { type: Boolean, default: false }
  }

  async perform({ params: { url: urlParam, query: queryParams } }) {
    this.isRunningValue = true
    const body = new FormData(this.element)

    if (queryParams) Object.keys(queryParams).forEach(key => body.append(key, queryParams[key]))

    const response = await patch(urlParam || this.urlValue, { body, responseKind: 'turbo-stream' })
    this.isRunningValue = false
    if (response.ok) this.countValue += 1
  }
}

Turbo Fetch Helper

Add this helper to your RSpec support files:

File: spec/support/helpers/turbo_fetch_helper.rb

module TurboFetchHelper
  def expect_turbo_fetch_request
    count_value = find("[data-controller='turbo-fetch']")['data-turbo-fetch-count-value'] || 0
    yield
    expect(page).to have_selector("[data-turbo-fetch-count-value='#{count_value.to_i + 1}']")
  end
end

View Integration for Testing

Add the turbo-fetch controller alongside form-auto-save:

= simple_form_for resource, html: { data: { controller: 'form-auto-save turbo-fetch', turbo_permanent: true } } do |f|
  = f.input :field_name
  = f.rich_text_area :content

System Spec Example

require 'rails_helper'

RSpec.describe 'Form Auto Save', :js do
  it 'automatically saves form after changes' do
    resource = create(:resource)
    visit edit_resource_path(resource)

    expect_turbo_fetch_request do
      fill_in 'Field name', with: 'Updated value'
    end

    expect(resource.reload.field_name).to eq('Updated value')
  end

  it 'debounces multiple rapid changes' do
    resource = create(:resource)
    visit edit_resource_path(resource)

    expect_turbo_fetch_request do
      fill_in 'Field name', with: 'First'
      fill_in 'Field name', with: 'Second'
      fill_in 'Field name', with: 'Final'
    end

    # Should only save once with final value
    expect(resource.reload.field_name).to eq('Final')
  end
end

Common Issues

Issue: Form doesn’t auto-save

Check:

  • Controller properly attached: data: { controller: 'form-auto-save' }
  • Form fields trigger change events (text inputs may need blur)
  • Network requests in browser DevTools

Issue: Too many requests

Solutions:

  • Increase DEBOUNCE_TIME
  • Check for unnecessary event triggers
  • Verify debounce logic is working

Issue: Lost changes on navigation

Solutions:

  • Add turbo_permanent: true to form
  • Ensure form has stable id attribute
  • Consider adding “unsaved changes” warning

Related Patterns

  • Turbo Streams: For more complex form updates and partial page replacements
  • Stimulus Values: If you need per-instance debounce times
  • Form Validation: Consider inline validation with auto-save

References