Skip to content

Commit

Permalink
Fire 'turbo:frame-render' event after turbo frame renders the view (#327
Browse files Browse the repository at this point in the history
)

* Fire 'turbo:after-fetch-render' event after turbo frame renders the view

* fixup! Fire 'turbo:after-fetch-render' event after turbo frame renders the view

* Change 'turbo:after-fetch-render' event to 'turbo:frame-render'

* Implement notifyApplicationAfterFrameRender in order to dispatch the 'turbo:frame-render' event

* Dispatch `turbo:frame-load` on turbo-frame

Closes #54
Closes hotwired/turbo-rails#56

Dispatch `turbo:frame-load` lifecycle event when `<turbo-frame>` element
is navigated and finishes loading. The events bubble up, with the
`<turbo-frame>` element as the target.

Originally, this pull request involved numerous events, but in the
spirit of experimentation, we'll start with the one and see if others
are necessary.

* fixup! Dispatch `turbo:frame-load` on turbo-frame

Co-authored-by: John Kapantzakis <kapantzak@192.168.2.2>
Co-authored-by: David Heinemeier Hansson <david@loudthinking.com>
Co-authored-by: Sean Doyle <sean.p.doyle24@gmail.com>
  • Loading branch information
4 people authored Aug 25, 2021
1 parent e7a0b91 commit 84b0a89
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
this.appearanceObserver.stop()
await this.element.loaded
this.hasBeenLoaded = true
session.frameLoaded(this.element)
} catch (error) {
this.currentURL = previousURL
throw error
Expand All @@ -108,6 +109,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false)
if (this.view.renderPromise) await this.view.renderPromise
await this.view.render(renderer)
session.frameRendered(fetchResponse, this.element);
}
} catch (error) {
console.error(error)
Expand Down Expand Up @@ -223,11 +225,9 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}

viewRenderedSnapshot(snapshot: Snapshot, isPreview: boolean) {

}

viewInvalidated() {

}

// Private
Expand Down
20 changes: 20 additions & 0 deletions src/core/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { dispatch } from "../util"
import { PageView, PageViewDelegate } from "./drive/page_view"
import { Visit, VisitOptions } from "./drive/visit"
import { PageSnapshot } from "./drive/page_snapshot"
import { FrameElement } from "../elements/frame_element"
import { FetchResponse } from "../http/fetch_response"

export type TimingData = {}

Expand Down Expand Up @@ -235,6 +237,16 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
this.adapter.pageInvalidated()
}

// Frame element

frameLoaded(frame: FrameElement) {
this.notifyApplicationAfterFrameLoad(frame)
}

frameRendered(fetchResponse: FetchResponse, frame: FrameElement) {
this.notifyApplicationAfterFrameRender(fetchResponse, frame);
}

// Application events

applicationAllowsFollowingLinkToLocation(link: Element, location: URL) {
Expand Down Expand Up @@ -279,6 +291,14 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }))
}

notifyApplicationAfterFrameLoad(frame: FrameElement) {
return dispatch("turbo:frame-load", { target: frame })
}

notifyApplicationAfterFrameRender(fetchResponse: FetchResponse, frame: FrameElement) {
return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true })
}

// Helpers

elementDriveEnabled(element?: Element) {
Expand Down
21 changes: 21 additions & 0 deletions src/tests/fixtures/frame_navigation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Turbo</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script src="/src/tests/fixtures/test.js"></script>
</head>
<body>
<div id="container">
<a id="outside" href="/src/tests/fixtures/frame_navigation.html" data-turbo-frame="frame">Outside Frame</a>

<turbo-frame id="frame">
<h2>Frame Navigation</h2>

<a id="inside" href="/src/tests/fixtures/frame_navigation.html">Inside Frame</a>
<a id="top" href="/src/tests/fixtures/frame_navigation.html" data-turbo-frame="_top">Top</a>
</turbo-frame>
</div>
</body>
</html>
4 changes: 4 additions & 0 deletions src/tests/fixtures/frames.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,9 @@ <h2>Frames: #nested-child</h2>

<a id="navigate-form-redirect" href="/src/tests/fixtures/frames/form-redirect.html" data-turbo-frame="form-redirect">Visit form-redirect.html</a>
<turbo-frame id="form-redirect"></turbo-frame>

<turbo-frame id="part">
<a id="frame-part" href="/src/tests/fixtures/frames/part.html">Load #part</a>
</turbo-frame>
</body>
</html>
3 changes: 3 additions & 0 deletions src/tests/fixtures/frames/part.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<turbo-frame id="part">
<h2>Frames: #frame-part</h2>
</turbo-frame>
6 changes: 4 additions & 2 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}

function eventListener(event) {
eventLogs.push([event.type, event.detail])
eventLogs.push([event.type, event.detail, event.target.id])
}
window.mutationLogs = []

Expand All @@ -26,5 +26,7 @@
"turbo:render",
"turbo:before-fetch-request",
"turbo:before-fetch-response",
"turbo:visit"
"turbo:visit",
"turbo:frame-load",
"turbo:frame-render",
])
21 changes: 21 additions & 0 deletions src/tests/functional/frame_navigation_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case"

export class FrameNavigationTests extends TurboDriveTestCase {
async setup() {
await this.goToLocation("/src/tests/fixtures/frame_navigation.html")
}

async "test frame navigation with descendant link"() {
await this.clickSelector("#inside")

await this.nextEventOnTarget("frame", "turbo:frame-load")
}

async "test frame navigation with exterior link"() {
await this.clickSelector("#outside")

await this.nextEventOnTarget("frame", "turbo:frame-load")
}
}

FrameNavigationTests.registerSuite()
13 changes: 11 additions & 2 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class FrameTests extends TurboDriveTestCase {
async "test a frame whose src references itself does not infinitely loop"() {
await this.clickSelector("#frame-self")

await this.nextEventNamed("turbo:before-fetch-response")
await this.nextEventOnTarget("frame", "turbo:frame-load")

const otherEvents = await this.eventLogChannel.read()
this.assert.equal(otherEvents.length, 0, "no more events")
Expand Down Expand Up @@ -152,7 +152,16 @@ export class FrameTests extends TurboDriveTestCase {
this.assert.ok(await this.querySelector("#form-redirect-header"))
}

async "test following a link reloads frame on every click"() {
async "test 'turbo:frame-render' is triggered after frame has finished rendering"() {
await this.clickSelector("#frame-part")

await this.nextEventNamed("turbo:frame-render") // recursive
const { fetchResponse } = await this.nextEventNamed("turbo:frame-render")

this.assert.include(fetchResponse.response.url, "/src/tests/fixtures/frames/part.html")
}

async "test following a link reloads frame on every click"() {
await this.clickSelector("#hello a")
await this.nextEventNamed("turbo:before-fetch-request")

Expand Down
1 change: 1 addition & 0 deletions src/tests/functional/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./drive_tests"
export * from "./form_submission_tests"
export * from "./frame_tests"
export * from "./import_tests"
export * from "./frame_navigation_tests"
export * from "./loading_tests"
export * from "./navigation_tests"
export * from "./pausable_rendering_tests"
Expand Down
11 changes: 10 additions & 1 deletion src/tests/helpers/turbo_drive_test_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FunctionalTestCase } from "./functional_test_case"
import { RemoteChannel } from "./remote_channel"
import { Element } from "@theintern/leadfoot"

type EventLog = [string, any]
type EventLog = [string, any, string | null]
type MutationLog = [string, string | null, string | null]

export class TurboDriveTestCase extends FunctionalTestCase {
Expand Down Expand Up @@ -40,6 +40,15 @@ export class TurboDriveTestCase extends FunctionalTestCase {
return !records.some(([name]) => name == eventName)
}

async nextEventOnTarget(elementId: string, eventName: string): Promise<any> {
let record: EventLog | undefined
while (!record) {
const records = await this.eventLogChannel.read(1)
record = records.find(([name, _, id]) => name == eventName && id == elementId)
}
return record[1]
}

async nextAttributeMutationNamed(elementId: string, attributeName: string): Promise<string | null> {
let record: MutationLog | undefined
while (!record) {
Expand Down

0 comments on commit 84b0a89

Please sign in to comment.