Skip to content

Commit

Permalink
Merge branch 'main' into form-submission-options
Browse files Browse the repository at this point in the history
  • Loading branch information
pythonandchips authored Oct 18, 2021
2 parents e8e8cca + 58d2261 commit 4a1a5c7
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 4 deletions.
18 changes: 18 additions & 0 deletions src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export class FormSubmission {
state = FormSubmissionState.initialized
result?: FormSubmissionResult

static confirmMethod(message: string, element: HTMLFormElement):boolean {
return confirm(message)
}

constructor(delegate: FormSubmissionDelegate, formElement: HTMLFormElement, submitter?: HTMLElement, mustRedirect = false) {
this.delegate = delegate
this.formElement = formElement
Expand Down Expand Up @@ -94,10 +98,24 @@ export class FormSubmission {
}, [] as [string, string][])
}

get confirmationMessage() {
return this.formElement.getAttribute("data-turbo-confirm")
}

get needsConfirmation() {
return this.confirmationMessage !== null
}

// The submission process

async start() {
const { initialized, requesting } = FormSubmissionState

if (this.needsConfirmation) {
const answer = FormSubmission.confirmMethod(this.confirmationMessage!, this.formElement)
if (!answer) { return }
}

if (this.state == initialized) {
this.state = requesting
return this.fetchRequest.perform()
Expand Down
5 changes: 5 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { StreamSource } from "./types"
import { VisitOptions } from "./drive/visit"
import { PageRenderer } from "./drive/page_renderer"
import { PageSnapshot } from "./drive/page_snapshot"
import { FormSubmission } from "./drive/form_submission"

const session = new Session
const { navigator } = session
Expand Down Expand Up @@ -100,3 +101,7 @@ export function setProgressBarDelay(delay: number) {
export function setFormMode(mode: string) {
session.setFormMode(mode)
}

export function setConfirmMethod(confirmMethod: (message: string, element: HTMLFormElement)=>boolean) {
FormSubmission.confirmMethod = confirmMethod
}
28 changes: 26 additions & 2 deletions src/core/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,25 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
form.action = link.getAttribute("href") || "undefined"
form.hidden = true

link.parentNode?.insertBefore(form, link)
if (link.hasAttribute("data-turbo-confirm")) {
form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm")!)
}

const frame = this.getTargetFrameForLink(link)
if (frame) {
form.setAttribute("data-turbo-frame", frame)
form.addEventListener("turbo:submit-start", () => form.remove())
} else {
form.addEventListener("submit", () => form.remove())
}

document.body.appendChild(form)
return dispatch("submit", { cancelable: true, target: form })
} else {
return false
}
}


// Navigator delegate

allowsVisitingLocationWithAction(location: URL, action?: Action) {
Expand Down Expand Up @@ -345,6 +356,19 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
return isAction(action) ? action : "advance"
}

getTargetFrameForLink(link: Element) {
const frame = link.getAttribute("data-turbo-frame")

if (frame) {
return frame
} else {
const container = link.closest("turbo-frame")
if (container) {
return container.id
}
}
}

get snapshot() {
return this.view.snapshot
}
Expand Down
19 changes: 17 additions & 2 deletions src/tests/fixtures/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ <h1>Form</h1>
</form>
<form action="/__turbo/submit" method="post" data-turbo="true" class="turbo-enabled">
<input type="hidden" name="query" value="2">
</form>
<form action="/__turbo/redirect" method="post" class="confirm" data-turbo-confirm="Are you sure?">
<input type="submit">
</form>
</div>
Expand Down Expand Up @@ -197,7 +199,15 @@ <h2>Frame: Form</h2>
<input type="hidden" name="content" value="Hello!">
<input type="submit">
</form>
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="link-method-inside-frame">Stream link inside frame</a>
<a href="/src/tests/fixtures/frames/frame.html" data-turbo-method="get" id="link-method-inside-frame">Method link inside frame</a><br />
<a href="/src/tests/fixtures/frames/hello.html" data-turbo-method="get" data-turbo-frame="_top" id="link-method-inside-frame-target-top">Break-out of frame with method link inside frame</a><br />
<a href="/src/tests/fixtures/frames/hello.html" data-turbo-method="get" data-turbo-frame="hello" id="link-method-inside-frame-with-target">Method link inside frame targeting another frame</a><br />
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="stream-link-method-inside-frame">Stream link inside frame</a>
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" data-turbo-confirm="Are you sure?" id="link-method-inside-frame-with-confirmation"data-turbo-confirm="Are you sure?">Stream link inside frame with confirmation</a>
<form>
<a href="/src/tests/fixtures/frames/frame.html" data-turbo-method="get" id="method-link-within-form-inside-frame">Method link within form inside frame</a><br />
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="stream-link-method-within-form-inside-frame">Stream link within form inside frame</a>
</form>
<form action="/__turbo/messages/1" method="put" class="stream put">
<input type="hidden" name="type" value="stream">
<input type="hidden" name="content" value="Hello!">
Expand All @@ -220,7 +230,12 @@ <h2>Frame: Form</h2>
<div id="messages">
</div>
</turbo-frame>
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="link-method-outside-frame">Stream link outside frame</a>
<a href="/src/tests/fixtures/frames/hello.html" data-turbo-method="get" id="link-method-outside-frame">Method link outside frame</a><br />
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="stream-link-method-outside-frame">Stream link outside frame</a>
<form>
<a href="/src/tests/fixtures/frames/hello.html" data-turbo-method="get" id="link-method-within-form-outside-frame">Method link within form outside frame</a><br />
<a href="/__turbo/messages?content=Link!&type=stream" data-turbo-method="post" id="stream-link-method-within-form-outside-frame">Stream link within form outside frame</a>
</form>
<hr>
<turbo-frame id="hello"></turbo-frame>
</body>
Expand Down
106 changes: 106 additions & 0 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.notOk(await this.hasSelector(".turbo-progress-bar"), "hides progress bar")
}

async "test form submission with confirmation confirmed"() {
await this.clickSelector("#standard form.confirm input[type=submit]")

this.assert.equal(await this.getAlertText(), "Are you sure?")
await this.acceptAlert()
this.assert.ok(await this.formSubmitted)
}

async "test form submission with confirmation cancelled"() {
await this.clickSelector("#standard form.confirm input[type=submit]")

this.assert.equal(await this.getAlertText(), "Are you sure?")
await this.dismissAlert()
this.assert.notOk(await this.formSubmitted)
}

async "test from submission with confirmation overriden"() {
await this.remote.execute(() => window.Turbo.setConfirmMethod((message, element) => confirm("Overriden message")))

await this.clickSelector("#standard form.confirm input[type=submit]")

this.assert.equal(await this.getAlertText(), "Overriden message")
await this.acceptAlert()
this.assert.ok(await this.formSubmitted)
}

async "test standard form submission does not render a progress bar before expiring the delay"() {
await this.remote.execute(() => window.Turbo.setProgressBarDelay(500))
await this.clickSelector("#standard form.redirect input[type=submit]")
Expand Down Expand Up @@ -442,16 +468,96 @@ export class FormSubmissionTests extends TurboDriveTestCase {

async "test link method form submission inside frame"() {
await this.clickSelector("#link-method-inside-frame")
await this.nextBeat

const title = await this.querySelector("#frame h2")
this.assert.equal(await title.getVisibleText(), "Frame: Loaded")
this.assert.notOk(await this.hasSelector("#nested-child"))
}

async "test link method form submission inside frame with data-turbo-frame=_top"() {
await this.clickSelector("#link-method-inside-frame-target-top")
await this.nextBody

const title = await this.querySelector("h1")
this.assert.equal(await title.getVisibleText(), "Hello")
}

async "test link method form submission inside frame with data-turbo-frame target"() {
await this.clickSelector("#link-method-inside-frame-with-target")
await this.nextBeat

const title = await this.querySelector("h1")
const frameTitle = await this.querySelector("#hello h2")
this.assert.equal(await frameTitle.getVisibleText(), "Hello from a frame")
this.assert.equal(await title.getVisibleText(), "Form")
}

async "test stream link method form submission inside frame"() {
await this.clickSelector("#stream-link-method-inside-frame")
await this.nextBeat

const message = await this.querySelector("#frame div.message")
this.assert.equal(await message.getVisibleText(), "Link!")
}

async "test link method form submission within form inside frame"() {
await this.clickSelector("#stream-link-method-within-form-inside-frame")
await this.nextBeat

const message = await this.querySelector("#frame div.message")
this.assert.equal(await message.getVisibleText(), "Link!")
}

async "test link method form submission inside frame with confirmation confirmed"() {
await this.clickSelector("#link-method-inside-frame-with-confirmation")

this.assert.equal(await this.getAlertText(), "Are you sure?")
await this.acceptAlert()

await this.nextBeat

const message = await this.querySelector("#frame div.message")
this.assert.equal(await message.getVisibleText(), "Link!")
}

async "test link method form submission inside frame with confirmation cancelled"() {
await this.clickSelector("#link-method-inside-frame-with-confirmation")

this.assert.equal(await this.getAlertText(), "Are you sure?")
await this.dismissAlert()

await this.nextBeat

this.assert.notOk(await this.hasSelector("#frame div.message"), "Not confirming form submission does not submit the form")
}

async "test link method form submission outside frame"() {
await this.clickSelector("#link-method-outside-frame")
await this.nextBody

const title = await this.querySelector("h1")
this.assert.equal(await title.getVisibleText(), "Hello")
}

async "test stream link method form submission outside frame"() {
await this.clickSelector("#stream-link-method-outside-frame")
await this.nextBeat

const message = await this.querySelector("#frame div.message")
this.assert.equal(await message.getVisibleText(), "Link!")
}

async "test link method form submission within form outside frame"() {
await this.clickSelector("#link-method-within-form-outside-frame")
await this.nextBody

const title = await this.querySelector("h1")
this.assert.equal(await title.getVisibleText(), "Hello")
}

async "test stream link method form submission within form outside frame"() {
await this.clickSelector("#stream-link-method-within-form-outside-frame")
await this.nextBeat

const message = await this.querySelector("#frame div.message")
Expand Down

0 comments on commit 4a1a5c7

Please sign in to comment.