Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure Submitter disabling #1216

Merged
merged 2 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading