x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form class="card flex flex-col gap" data-controller="autosave" data-action="input->autosave#change" action="/slow_action" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="nYKJIwaOK6YSTQk2vjDXd8HMRS39_kpur06udy19v5UKOnMUVMzEra35CLrhKfyDBKBpQBRmou2ysQ37c8YzsQ" autocomplete="off" /> <div class="flex flex-col gap-half"> <h3 class="text-2xl font-semibold leading-none">Autosave</h3> <p class="text-sm text-subtle">Your changes will be saved automatically without the need to click on the save changes button.</p> </div> <div class="flex flex-col gap mb-2"> <div class="flex flex-col gap-half"> <label class="text-sm font-medium leading-none" for="content">Content</label> <textarea rows="10" class="input" name="content" id="content"></textarea> </div> </div> <div class="flex items-center"> <button name="button" type="submit" class="btn btn--primary btn--loading" data-autosave-target="submitter"><span>Save changes</span></button> </div></form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%= form_with url: main_app.slow_action_path, class: "card flex flex-col gap", data: { controller: "autosave", action: "input->autosave#change" } do |form| %> <div class="flex flex-col gap-half"> <h3 class="text-2xl font-semibold leading-none">Autosave</h3> <p class="text-sm text-subtle">Your changes will be saved automatically without the need to click on the save changes button.</p> </div> <div class="flex flex-col gap mb-2"> <div class="flex flex-col gap-half"> <%= form.label :content, class: "text-sm font-medium leading-none" %> <%= form.text_area :content, rows: 10, class: "input" %> </div> </div> <div class="flex items-center"> <%= form.button tag.span("Save changes"), class: "btn btn--primary btn--loading", data: { autosave_target: "submitter" } %> </div><% end %>
CSS is not required or multiple files are needed, check the notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { Controller } from "@hotwired/stimulus"import { FetchRequest } from "https://cdn.skypack.dev/@rails/request.js@0.0.11?min"const AUTOSAVE_INTERVAL = 3000export default class extends Controller { static targets = [ "submitter" ] #timer disconnect() { this.#submit() } change() { !this.#dirty && this.#scheduleSave() !this.#dirty && this.#updateAppearance() } async #submit() { this.#dirty && await this.#save() } async #save() { this.#updateAppearance(true) this.#resetTimer() await this.#submitForm(this.element) this.#updateAppearance() } async #submitForm(form) { const request = new FetchRequest(form.method, form.action, { body: new FormData(form) }) return await request.perform() } #updateAppearance(saving = false) { if (saving) { this.element.setAttribute("aria-busy", true) this.submitterTarget.setAttribute("aria-disabled", true) this.submitterTarget.disabled = true } else { this.element.removeAttribute("aria-busy") this.submitterTarget.removeAttribute("aria-disabled") this.submitterTarget.disabled = false } } #scheduleSave() { this.#timer = setTimeout(() => this.#save(), AUTOSAVE_INTERVAL) } #resetTimer() { clearTimeout(this.#timer); this.#timer = null } get #dirty() { return !!this.#timer }}
No notes provided.