Automatic form submission after user input changes using a debounce mechanism to prevent excessive server requests. Creates a seamless auto-save experience for forms with rich text editors or multiple fields.
59
74%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./skills/form-auto-save/SKILL.mdThe 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.
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:
static DEBOUNCE_TIME)change and lexxy:change events (for custom components)cancel() and submit() methods for programmatic controlcountValue, which system specs wait onController Code Pattern:
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static DEBOUNCE_TIME = 8000
static values = { count: Number }
connect() {
this.element.addEventListener('change', this.#debounceSubmit.bind(this), { passive: true })
this.element.addEventListener('lexxy:change', this.#debounceSubmit.bind(this), { passive: true })
this.element.addEventListener('turbo:submit-end', ({ detail }) => { if (detail.success) this.countValue++ })
}
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)
}
}Attach the controller to the form element using Stimulus data attributes.
Required Attributes:
data: { controller: 'form-auto-save' } - Attaches the Stimulus controllerdata: { turbo_permanent: true } - Optional but recommended to preserve form state during Turbo navigationExample (Slim):
= simple_form_for resource, html: { data: { controller: 'form-auto-save', turbo_permanent: true } } do |f|
= f.input :field_name
= f.rich_text_area :contentstatic DEBOUNCE_TIME in the controller if neededchange events (standard HTML input changes)lexxy:change events (custom component events, like rich text editors)turbo_permanent: true keeps the form element across Turbo navigationWait on the controller's countValue, not on sleep timers. It goes up when Turbo reports a successful submission, so it covers the debounce and the round trip. turbo_permanent: true keeps the form, and its count, when the response redirects.
File: spec/support/helpers/form_auto_save_helper.rb
module FormAutoSaveHelper
def expect_auto_save
form = find("[data-controller~='form-auto-save']")
count = form['data-form-auto-save-count-value'].to_i
yield
expect(page).to have_css("[data-form-auto-save-count-value='#{count + 1}']", wait: 10)
end
endInclude it for system specs in spec/support/helpers.rb with c.include FormAutoSaveHelper, type: :system. The wait: must be longer than DEBOUNCE_TIME.
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_auto_save 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_auto_save 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
endCheck:
data: { controller: 'form-auto-save' }change events (text inputs may need blur)Solutions:
DEBOUNCE_TIMESolutions:
turbo_permanent: true to formid attributedynamic-forms skill (the turbo_form gem)cb80143
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.