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 Nov 16, 2023
1 parent a247b35 commit 3d723eb
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 25 deletions.
28 changes: 24 additions & 4 deletions src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export class FrameController {

// View delegate

allowsImmediateRender({ element: newFrame }, _isPreview, options) {
allowsImmediateRender({ element: newFrame }, _renderer, options) {
const event = dispatch("turbo:before-frame-render", {
target: this.element,
detail: { newFrame, ...options },
Expand All @@ -276,7 +276,13 @@ export class FrameController {
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 @@ -311,9 +317,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 @@ -590,3 +598,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)
}
}
24 changes: 6 additions & 18 deletions src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ export class Session {
}
}

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

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

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

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

// Application events

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

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

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

Expand All @@ -363,14 +359,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
6 changes: 3 additions & 3 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, newSnapshot: snapshot } = renderer
const { shouldRender, newSnapshot: snapshot } = renderer
if (shouldRender) {
try {
this.renderPromise = new Promise((resolve) => (this.#resolveRenderPromise = resolve))
Expand All @@ -65,11 +65,11 @@ export class View {

const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve))
const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement }
const immediateRender = this.delegate.allowsImmediateRender(snapshot, isPreview, options)
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

0 comments on commit 3d723eb

Please sign in to comment.