Skip to content

Commit

Permalink
Toggle [disabled] on form submitter (#386)
Browse files Browse the repository at this point in the history
During a form submission, toggle the [disabled][] attribute on prior to
`turbo:submit-start`, and remove it prior to firing `turbo:submit-end`.

If callers need to control the submitter's [disabled][] attribute more
finely, they can declare listeners to do so.

Combined with CSS rules, consumer applications can hide and show
submission text similar to RailsUJS's support for `data-disable-with`:

```css
button                  .show-when-disabled { display: none; }
button[disabled]        .show-when-disabled { display: initial; }

button                  .show-when-enabled { display: initial; }
button[disabled]        .show-when-enabled { display: none; }
```

```html
<button>
  <span class="show-when-enabled">Submit</span>
  <span class="show-when-disabled">Submitting...</span>
</button>
```

Styling descendant submitters based on their ancestors is only possible
with `<button>` elements, since `<input type="submit">` do not have text
content or descendants of their own.

[disabled]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
  • Loading branch information
seanpdoyle committed Nov 9, 2021
1 parent dbd1e9b commit 7b54d10
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export class FormSubmission {

requestStarted(request: FetchRequest) {
this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } })
this.delegate.formSubmissionStarted(this)
}
Expand Down Expand Up @@ -178,6 +179,7 @@ export class FormSubmission {

requestFinished(request: FetchRequest) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
dispatch("turbo:submit-end", { target: this.formElement, detail: { formSubmission: this, ...this.result }})
this.delegate.formSubmissionFinished(this)
}
Expand Down
28 changes: 28 additions & 0 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ export class FormSubmissionTests extends TurboDriveTestCase {
await this.nextEventNamed("turbo:load")
}

async "test standard POST form submission toggles submitter [disabled] attribute"() {
await this.clickSelector("#standard-post-form-submit")

this.assert.equal(await this.nextAttributeMutationNamed("standard-post-form-submit", "disabled"), "", "sets [disabled] on the submitter")
this.assert.equal(await this.nextAttributeMutationNamed("standard-post-form-submit", "disabled"), null, "removes [disabled] from the submitter")
}

async "test standard GET form submission"() {
await this.clickSelector("#standard form.greeting input[type=submit]")
await this.nextBody
Expand Down Expand Up @@ -117,6 +124,13 @@ export class FormSubmissionTests extends TurboDriveTestCase {
await this.nextEventNamed("turbo:load")
}

async "test standard GET form submission toggles submitter [disabled] attribute"() {
await this.clickSelector("#standard-get-form-submit")

this.assert.equal(await this.nextAttributeMutationNamed("standard-get-form-submit", "disabled"), "", "sets [disabled] on the submitter")
this.assert.equal(await this.nextAttributeMutationNamed("standard-get-form-submit", "disabled"), null, "removes [disabled] from the submitter")
}

async "test standard GET form submission appending keys"() {
await this.goToLocation("/src/tests/fixtures/form.html?query=1")
await this.clickSelector("#standard form.conflicting-values input[type=submit]")
Expand Down Expand Up @@ -345,6 +359,13 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(otherEvents.length, 0, "no more events")
}

async "test frame POST form targetting frame toggles submitter's [disabled] attribute"() {
await this.clickSelector("#targets-frame-post-form-submit")

this.assert.equal(await this.nextAttributeMutationNamed("targets-frame-post-form-submit", "disabled"), "", "sets [disabled] on the submitter")
this.assert.equal(await this.nextAttributeMutationNamed("targets-frame-post-form-submit", "disabled"), null, "removes [disabled] from the submitter")
}

async "test frame GET form targetting frame submission"() {
await this.clickSelector("#targets-frame-get-form-submit")

Expand All @@ -366,6 +387,13 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(otherEvents.length, 0, "no more events")
}

async "test frame GET form targetting frame toggles submitter's [disabled] attribute"() {
await this.clickSelector("#targets-frame-get-form-submit")

this.assert.equal(await this.nextAttributeMutationNamed("targets-frame-get-form-submit", "disabled"), "", "sets [disabled] on the submitter")
this.assert.equal(await this.nextAttributeMutationNamed("targets-frame-get-form-submit", "disabled"), null, "removes [disabled] from the submitter")
}

async "test frame form GET submission from submitter referencing another frame"() {
await this.clickSelector("#frame form[method=get] [type=submit][data-turbo-frame=hello]")
await this.nextBeat
Expand Down

0 comments on commit 7b54d10

Please sign in to comment.