Skip to content

Commit

Permalink
Make Renderer instance available to View delegates
Browse files Browse the repository at this point in the history
The changes proposed in [hotwired#1019][] require dispatching the new
`Renderer.renderMethod` property as part of the `turbo:render` event.
Similarly, there's an opportunity to include that information in the
`turbo:before-render`, `turbo:before-frame-render`, and
`turbo:frame-render` events.

To simplify those chains of object access, this commit changes the
`View` class delegate's `allowsImmediateRender` and
`viewRenderedSnapshot` methods to accept the `Renderer` instance,
instead of individual properties.

With access to the instance, the delegate's can read properties like
`isPreview` along with the `element` (transitively through the
`newSnapshot` property).

In order to dispatch the `turbo:frame-render` event closer to the moment
in time that the view renders, this commit removes the
`Session.frameRendered` callback, and replaces it with a
`dispatch("turbo:frame-render")` call inside the
`FrameController.viewRenderedSnapshot(renderer)` delegate method.

In order to do so, this commit must work around the fact that
`turbo:frame-render` events include the `FetchResponse` instance
involved in the render. Since that instance isn't available at render
time, this commit wraps the `await this.view.render(renderer)` in a
utility function that injects the `FetchResponse` instance into the
Custom event's `event.detail` object immediately after it's initially
dispatched.

Ideally, this work around will only be temporary, since the
`turbo:frame-load` event also includes `event.detail.fetchResponse`.
There's an opportunity to deprecate that property in
`turbo:frame-render` events in the future.

[hotwired#1019]: hotwired#1019
  • Loading branch information
seanpdoyle committed Mar 29, 2024
1 parent 9fb05e3 commit 82c4690
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 33 deletions.
34 changes: 27 additions & 7 deletions src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,25 +250,31 @@ export class FrameController {

// View delegate

allowsImmediateRender({ element: newFrame }, options) {
allowsImmediateRender({ element: newFrame }, renderer, options) {
const event = dispatch("turbo:before-frame-render", {
target: this.element,
detail: { newFrame, ...options },
detail: { newFrame, renderMethod: renderer.renderMethod, ...options },
cancelable: true
})
const {
defaultPrevented,
detail: { render }
} = event

if (this.view.renderer && render) {
this.view.renderer.renderElement = render
if (renderer && render) {
renderer.renderElement = render
}

return !defaultPrevented
}

viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}
viewRenderedSnapshot({ currentSnapshot: { element: frame }, renderMethod }) {
return dispatch("turbo:frame-render", {
detail: { renderMethod },
target: frame,
cancelable: true
})
}

preloadOnLoadLinksForView(element) {
session.preloadOnLoadLinksForView(element)
Expand Down Expand Up @@ -303,9 +309,11 @@ export class FrameController {
if (this.view.renderPromise) await this.view.renderPromise
this.changeHistory()

await this.view.render(renderer)
await mergeIntoNext("turbo:frame-render", { on: this.element, detail: { fetchResponse } }, async () => {
await this.view.render(renderer)
})

this.complete = true
session.frameRendered(fetchResponse, this.element)
session.frameLoaded(this.element)
await this.fetchResponseLoaded(fetchResponse)
} else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
Expand Down Expand Up @@ -580,3 +588,15 @@ function activateElement(element, currentURL) {
}
}
}

async function mergeIntoNext(eventName, { on: target, detail }, callback) {
const listener = (event) => Object.assign(event.detail, detail)
const listenerOptions = { once: true, capture: true }
target.addEventListener(eventName, listener, listenerOptions)

try {
await callback()
} finally {
target.removeEventListener(eventName, listener, listenerOptions)
}
}
28 changes: 8 additions & 20 deletions src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,8 @@ export class Session {
}
}

allowsImmediateRender({ element }, options) {
const event = this.notifyApplicationBeforeRender(element, options)
allowsImmediateRender({ element }, renderer, options) {
const event = this.notifyApplicationBeforeRender(element, renderer, options)
const {
defaultPrevented,
detail: { render }
Expand All @@ -326,9 +326,9 @@ export class Session {
return !defaultPrevented
}

viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {
viewRenderedSnapshot(renderer) {
this.view.lastRenderedLocation = this.history.location
this.notifyApplicationAfterRender(renderMethod)
this.notifyApplicationAfterRender(renderer)
}

preloadOnLoadLinksForView(element) {
Expand All @@ -345,10 +345,6 @@ export class Session {
this.notifyApplicationAfterFrameLoad(frame)
}

frameRendered(fetchResponse, frame) {
this.notifyApplicationAfterFrameRender(fetchResponse, frame)
}

// Application events

applicationAllowsFollowingLinkToLocation(link, location, ev) {
Expand Down Expand Up @@ -384,15 +380,15 @@ export class Session {
return dispatch("turbo:before-cache")
}

notifyApplicationBeforeRender(newBody, options) {
notifyApplicationBeforeRender(newBody, { renderMethod }, options) {
return dispatch("turbo:before-render", {
detail: { newBody, ...options },
detail: { newBody, renderMethod, ...options },
cancelable: true
})
}

notifyApplicationAfterRender(renderMethod) {
return dispatch("turbo:render", { detail: { renderMethod } })
notifyApplicationAfterRender({ isPreview, renderMethod }) {
return dispatch("turbo:render", { detail: { isPreview, renderMethod } })
}

notifyApplicationAfterPageLoad(timing = {}) {
Expand All @@ -414,14 +410,6 @@ export class Session {
return dispatch("turbo:frame-load", { target: frame })
}

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

// Helpers

submissionIsNavigatable(form, submitter) {
Expand Down
8 changes: 4 additions & 4 deletions src/core/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class View {
// Rendering

async render(renderer) {
const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer
const { shouldRender, willRender, newSnapshot: snapshot } = renderer

// A workaround to ignore tracked element mismatch reloads when performing
// a promoted Visit from a frame navigation
Expand All @@ -69,12 +69,12 @@ export class View {
await this.prepareToRenderSnapshot(renderer)

const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve))
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod }
const immediateRender = this.delegate.allowsImmediateRender(snapshot, options)
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement }
const immediateRender = this.delegate.allowsImmediateRender(snapshot, renderer, options)
if (!immediateRender) await renderInterception

await this.renderSnapshot(renderer)
this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod)
this.delegate.viewRenderedSnapshot(renderer)
this.delegate.preloadOnLoadLinksForView(this.element)
this.finishRenderingSnapshot(renderer)
} finally {
Expand Down
2 changes: 1 addition & 1 deletion src/tests/functional/frame_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ test("'turbo:frame-render' is triggered after frame has finished rendering", asy
await page.click("#frame-part")

await nextEventNamed(page, "turbo:frame-render") // recursive
const { fetchResponse } = await nextEventNamed(page, "turbo:frame-render")
const { fetchResponse } = await nextEventNamed(page, "turbo:frame-render", { renderMethod: "replace" })

assert.include(fetchResponse.response.url, "/src/tests/fixtures/frames/part.html")
})
Expand Down
2 changes: 1 addition & 1 deletion src/tests/functional/rendering_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test("triggers before-render and render events", async ({ page }) => {

assert.equal(await page.textContent("h1"), "One")

await nextEventNamed(page, "turbo:render")
await nextEventNamed(page, "turbo:render", { renderMethod: "replace" })
assert.equal(await newBody, await page.evaluate(() => document.body.outerHTML))
})

Expand Down

0 comments on commit 82c4690

Please sign in to comment.