Skip to content

Commit

Permalink
Merge pull request #1216 from seanpdoyle/config-disable-submitter
Browse files Browse the repository at this point in the history
Configure Submitter disabling
  • Loading branch information
jorgemanrubia authored Aug 14, 2024
2 parents f88bfe4 + 145faee commit 6646957
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 20 deletions.
4 changes: 4 additions & 0 deletions src/core/config/drive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const drive = {
enabled: true,
progressBarDelay: 500
}
41 changes: 41 additions & 0 deletions src/core/config/forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cancelEvent } from "../../util"

const submitter = {
"aria-disabled": {
beforeSubmit: submitter => {
submitter.setAttribute("aria-disabled", "true")
submitter.addEventListener("click", cancelEvent)
},

afterSubmit: submitter => {
submitter.removeAttribute("aria-disabled")
submitter.removeEventListener("click", cancelEvent)
}
},

"disabled": {
beforeSubmit: submitter => submitter.disabled = true,
afterSubmit: submitter => submitter.disabled = false
}
}

class Config {
#submitter = null

constructor(config) {
Object.assign(this, config)
}

get submitter() {
return this.#submitter
}

set submitter(value) {
this.#submitter = submitter[value] || value
}
}

export const forms = new Config({
mode: "on",
submitter: "disabled"
})
7 changes: 7 additions & 0 deletions src/core/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { drive } from "./drive"
import { forms } from "./forms"

export const config = {
drive,
forms
}
13 changes: 9 additions & 4 deletions src/core/drive/form_submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expandURL } from "../url"
import { clearBusyState, dispatch, getAttribute, getMetaContent, hasAttribute, markAsBusy } from "../../util"
import { StreamMessage } from "../streams/stream_message"
import { prefetchCache } from "./prefetch_cache"
import { config } from "../config"

export const FormSubmissionState = {
initialized: "initialized",
Expand All @@ -22,7 +23,7 @@ export const FormEnctype = {
export class FormSubmission {
state = FormSubmissionState.initialized

static confirmMethod(message, _element, _submitter) {
static confirmMethod(message) {
return Promise.resolve(confirm(message))
}

Expand Down Expand Up @@ -78,7 +79,11 @@ export class FormSubmission {
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement)

if (typeof confirmationMessage === "string") {
const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter)
const confirmMethod = typeof config.forms.confirm === "function" ?
config.forms.confirm :
FormSubmission.confirmMethod

const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter)
if (!answer) {
return
}
Expand Down Expand Up @@ -116,7 +121,7 @@ export class FormSubmission {

requestStarted(_request) {
this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter)
this.setSubmitsWith()
markAsBusy(this.formElement)
dispatch("turbo:submit-start", {
Expand Down Expand Up @@ -162,7 +167,7 @@ export class FormSubmission {

requestFinished(_request) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
if (this.submitter) config.forms.submitter.afterSubmit(this.submitter)
this.resetSubmitterText()
clearBusyState(this.formElement)
dispatch("turbo:submit-end", {
Expand Down
19 changes: 14 additions & 5 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { Session } from "./session"
import { PageRenderer } from "./drive/page_renderer"
import { PageSnapshot } from "./drive/page_snapshot"
import { FrameRenderer } from "./frames/frame_renderer"
import { FormSubmission } from "./drive/form_submission"
import { fetch, recentRequests } from "../http/fetch"
import { config } from "./config"

const session = new Session(recentRequests)
const { cache, navigator } = session
export { navigator, session, cache, PageRenderer, PageSnapshot, FrameRenderer, fetch }
export { navigator, session, cache, PageRenderer, PageSnapshot, FrameRenderer, fetch, config }

/**
* Starts the main session.
Expand Down Expand Up @@ -97,13 +97,22 @@ export function clearCache() {
* @param delay Time to delay in milliseconds
*/
export function setProgressBarDelay(delay) {
session.setProgressBarDelay(delay)
console.warn(
"Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
)
config.drive.progressBarDelay = delay
}

export function setConfirmMethod(confirmMethod) {
FormSubmission.confirmMethod = confirmMethod
console.warn(
"Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
)
config.forms.confirm = confirmMethod
}

export function setFormMode(mode) {
session.setFormMode(mode)
console.warn(
"Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`"
)
config.forms.mode = mode
}
22 changes: 14 additions & 8 deletions src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { PageView } from "./drive/page_view"
import { FrameElement } from "../elements/frame_element"
import { Preloader } from "./drive/preloader"
import { Cache } from "./cache"
import { config } from "./config"

export class Session {
navigator = new Navigator(this)
Expand All @@ -37,11 +38,8 @@ export class Session {
streamMessageRenderer = new StreamMessageRenderer()
cache = new Cache(this)

drive = true
enabled = true
progressBarDelay = 500
started = false
formMode = "on"
#pageRefreshDebouncePeriod = 150

constructor(recentRequests) {
Expand Down Expand Up @@ -131,11 +129,19 @@ export class Session {
}

setProgressBarDelay(delay) {
console.warn(
"Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`"
)

this.progressBarDelay = delay
}

setFormMode(mode) {
this.formMode = mode
set progressBarDelay(delay) {
config.drive.progressBarDelay = delay
}

get progressBarDelay() {
return config.drive.progressBarDelay
}

get location() {
Expand Down Expand Up @@ -425,12 +431,12 @@ export class Session {
// Helpers

submissionIsNavigatable(form, submitter) {
if (this.formMode == "off") {
if (config.forms.mode == "off") {
return false
} else {
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true

if (this.formMode == "optin") {
if (config.forms.mode == "optin") {
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null
} else {
return submitterIsNavigatable && this.elementIsNavigatable(form)
Expand All @@ -443,7 +449,7 @@ export class Session {
const withinFrame = findClosestRecursively(element, "turbo-frame")

// Check if Drive is enabled on the session or we're within a Frame.
if (this.drive || withinFrame) {
if (config.drive.enabled || withinFrame) {
// Element is navigatable by default, unless `data-turbo="false"`.
if (container) {
return container.getAttribute("data-turbo") != "false"
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/drive_disabled.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
})
</script>
<script>
Turbo.session.drive = false
Turbo.config.drive = false
</script>
</head>
<body>
Expand Down
68 changes: 66 additions & 2 deletions src/tests/functional/form_submission_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,22 @@ test("standard POST form submission toggles submitter [disabled] attribute", asy
)
})

test("standard POST form submission toggles submitter [aria-disabled=true] attribute", async ({ page }) => {
await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled")
await page.click("#standard-post-form-submit")

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

test("replaces input value with data-turbo-submits-with on form submission", async ({ page }) => {
page.click("#submits-with-form-input")

Expand Down Expand Up @@ -410,6 +426,22 @@ test("standard GET form submission toggles submitter [disabled] attribute", asyn
)
})

test("standard GET form submission toggles submitter [aria-disabled] attribute", async ({ page }) => {
await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled")
await page.click("#standard-get-form-submit")

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

test("standard GET form submission appending keys", async ({ page }) => {
await page.goto("/src/tests/fixtures/form.html?query=1")
await page.click("#standard form.conflicting-values input[type=submit]")
Expand Down Expand Up @@ -692,6 +724,22 @@ test("frame POST form targeting frame toggles submitter's [disabled] attribute",
)
})

test("frame POST form targeting frame toggles submitter's [aria-disabled] attribute", async ({ page }) => {
await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled")
await page.click("#targets-frame-post-form-submit")

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

test("frame GET form targeting frame submission", async ({ page }) => {
await page.click("#targets-frame-get-form-submit")

Expand Down Expand Up @@ -731,6 +779,22 @@ test("frame GET form targeting frame toggles submitter's [disabled] attribute",
)
})

test("frame GET form targeting frame toggles submitter's [aria-disabled] attribute", async ({ page }) => {
await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled")
await page.click("#targets-frame-get-form-submit")

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

test("frame form GET submission from submitter referencing another frame", async ({ page }) => {
await page.click("#frame form[method=get] [type=submit][data-turbo-frame=hello]")
await nextBeat()
Expand Down Expand Up @@ -1142,7 +1206,7 @@ test("following a link with [data-turbo-method] and [data-turbo=true] set when h
test("following a link with [data-turbo-method] and [data-turbo=true] set when Turbo.session.drive = false", async ({
page
}) => {
await page.evaluate(() => (window.Turbo.session.drive = false))
await page.evaluate(() => (window.Turbo.config.drive = false))

const link = await page.locator("#turbo-method-post-to-targeted-frame")
await link.evaluate((link) => link.setAttribute("data-turbo", "true"))
Expand All @@ -1163,7 +1227,7 @@ test("following a link with [data-turbo-method] set when html[data-turbo=false]"
})

test("following a link with [data-turbo-method] set when Turbo.session.drive = false", async ({ page }) => {
await page.evaluate(() => (window.Turbo.session.drive = false))
await page.evaluate(() => (window.Turbo.config.drive = false))
await page.click("#turbo-method-post-to-targeted-frame")

assert.equal(await page.textContent("h1"), "Hello", "treats link full-page navigation")
Expand Down
1 change: 1 addition & 0 deletions src/tests/unit/export_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test("Turbo interface", () => {
assert.equal(typeof Turbo.setConfirmMethod, "function")
assert.equal(typeof Turbo.setFormMode, "function")
assert.equal(typeof Turbo.cache, "object")
assert.equal(typeof Turbo.config, "object")
assert.equal(typeof Turbo.cache.clear, "function")
assert.equal(typeof Turbo.navigator, "object")
assert.equal(typeof Turbo.session, "object")
Expand Down
5 changes: 5 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export function dispatch(eventName, { target, cancelable, detail } = {}) {
return event
}

export function cancelEvent(event) {
event.preventDefault()
event.stopImmediatePropagation()
}

export function nextRepaint() {
if (document.visibilityState === "hidden") {
return nextEventLoopTick()
Expand Down

0 comments on commit 6646957

Please sign in to comment.