Skip to content

Commit

Permalink
allow customizing the element that Drive replaces
Browse files Browse the repository at this point in the history
While most of the time, replacing `<body>` makes perfect sense, when working with 3rd party integrations (i.e. Stripe), there are elements injected just before the closing `</body>` tag that should not be
removed between page visits. There is more detail on this issue, particularly with injected `<iframe>` elements in hotwired#305 (comment).

Now, if someone wants to customize the element that is replaced by Drive, they can add `data-turbo-drive-body` to an element, and only that element will be replaced between visits.
  • Loading branch information
agrobbin committed Jul 14, 2022
1 parent 98cdc40 commit 05d125e
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 2 deletions.
18 changes: 16 additions & 2 deletions src/core/drive/page_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Renderer } from "../renderer"
import { PageSnapshot } from "./page_snapshot"
import { ReloadReason } from "../native/browser_adapter"
import { nextEventLoopTick } from "../../util"

export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
get shouldRender() {
Expand Down Expand Up @@ -104,9 +105,14 @@ export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
}
}

assignNewBody() {
async assignNewBody() {
await nextEventLoopTick()

if (document.body && this.newElement instanceof HTMLBodyElement) {
document.body.replaceWith(this.newElement)
const currentBody = this.findBodyElement(document.body)
const newBody = this.findBodyElement(this.newElement)

currentBody.replaceWith(newBody)
} else {
document.documentElement.appendChild(this.newElement)
}
Expand All @@ -131,4 +137,12 @@ export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
get newBodyScriptElements() {
return this.newElement.querySelectorAll("script")
}

// Private

findBodyElement(body: HTMLBodyElement) {
if (!this.currentSnapshot.bodyElementId) return body

return body.querySelector(`#${this.currentSnapshot.bodyElementId}`) || body
}
}
4 changes: 4 additions & 0 deletions src/core/drive/page_snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export class PageSnapshot extends Snapshot<HTMLBodyElement> {
return this.getSetting("visit-control") != "reload"
}

get bodyElementId() {
return this.getSetting("body")
}

// Private

getSetting(name: string) {
Expand Down
21 changes: 21 additions & 0 deletions src/tests/fixtures/drive_custom_body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="turbo-body" content="app">
<title>Drive (with custom body)</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script src="/src/tests/fixtures/test.js"></script>
</head>
<body>
<h1>Drive (with custom body)</h1>

<div id="app">
<div>
<a id="drive" href="/src/tests/fixtures/drive_custom_body_2.html">Drive enabled link</a>
</div>

<p id="different-content">Drive 1</p>
</div>
</body>
</html>
21 changes: 21 additions & 0 deletions src/tests/fixtures/drive_custom_body_2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="turbo-body" content="app">
<title>Drive (with custom body)</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script src="/src/tests/fixtures/test.js"></script>
</head>
<body>
<h1>Drive (with custom body 2)</h1>

<div id="app">
<div>
<a id="drive" href="/src/tests/fixtures/drive_custom_body.html">Drive enabled link</a>
</div>

<p id="different-content">Drive 2</p>
</div>
</body>
</html>
23 changes: 23 additions & 0 deletions src/tests/functional/drive_custom_body_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case"

export class DriveCustomBodyTests extends TurboDriveTestCase {
path = "/src/tests/fixtures/drive_custom_body.html"

async setup() {
await this.goToLocation(this.path)
}

async "test drive with a custom body element"() {
this.clickSelector("#drive")
await this.nextBody

const h1 = await this.querySelector("h1")
const differentContent = await this.querySelector("different-content")

this.assert.equal(await this.pathname, this.path)
this.assert.equal(await h1.textContent, "Drive (with custom body)")
this.assert.equal(await differentContent.textContent, "Drive 2")
}
}

DriveCustomBodyTests.registerSuite()

0 comments on commit 05d125e

Please sign in to comment.