Skip to content

Commit

Permalink
Document turbo:before-{,frame,stream}-render events
Browse files Browse the repository at this point in the history
Documents [hotwired/turbo#431][]
Documents [hotwired/turbo#684][]

Share examples for `turbo:before-render`, `turbo:before-frame-render`,
and `turbo:before-stream-render` events that override the
`event.detail.render` function.

Additionally, document pausable rendering for `<turbo-frame>` elements.

[hotwired/turbo#431]: hotwired/turbo#431
[hotwired/turbo#684]: hotwired/turbo#684
  • Loading branch information
seanpdoyle committed Dec 29, 2022
1 parent 67bc88f commit 4231925
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 6 deletions.
26 changes: 22 additions & 4 deletions _source/handbook/02_drive.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,33 @@ Listen for the `turbo:before-visit` event to be notified when a visit is about t

Restoration visits cannot be canceled and do not fire `turbo:before-visit`. Turbo Drive issues restoration visits in response to history navigation that has *already taken place*, typically via the browser’s Back or Forward buttons.

## Custom Rendering

Turbo Drive's default rendering replaces the contents of the requesting document's `<body>` element with the contents of the response document's `<body>` element.

Applications can customize the rendering process by adding a document-wide `turbo:before-render` event listener and overriding the `event.detail.render` property.

For example, you could merge the response document's `<body>` element into the requesting document's `<body>` element with [morphdom](https://github.com/patrick-steele-idem/morphdom):

```javascript
import morphdom from "morphdom"

addEventListener("turbo:before-render", (event) => {
event.detail.render = (currentElement, newElement) => {
morphdom(currentElement, newElement)
}
})
```

## Pausing Rendering

Application can pause rendering and make additional preparation before it will be executed.
Applications can pause rendering and make additional preparations before continuing.

Listen for the `turbo:before-render` event to be notified when rendering is about to start, and pause it using `event.preventDefault()`. Once the preparation is done continue rendering by calling `event.detail.resume()`.

An example use case is adding exit animation for visits:
```javascript
document.addEventListener('turbo:before-render', async (event) => {
document.addEventListener("turbo:before-render", async (event) => {
event.preventDefault()

await animateOut()
Expand All @@ -98,11 +116,11 @@ Listen for the `turbo:before-fetch-request` event to be notified when a request

An example use case is setting `Authorization` header for the request:
```javascript
document.addEventListener('turbo:before-fetch-request', async (event) => {
document.addEventListener("turbo:before-fetch-request", async (event) => {
event.preventDefault()

const token = await getSessionToken(window.app)
event.detail.fetchOptions.headers['Authorization'] = `Bearer ${token}`
event.detail.fetchOptions.headers["Authorization"] = `Bearer ${token}`

event.detail.resume()
})
Expand Down
38 changes: 38 additions & 0 deletions _source/handbook/03_frames.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,41 @@ Turbo provides [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
```

Upon form submissions, the token will be automatically added to the request's headers as `X-CSRF-TOKEN`. Requests made with `data-turbo="false"` will skip adding the token to headers.

## Custom Rendering

Turbo's default `<turbo-frame>` rendering process replaces the contents of the requesting `<turbo-frame>` element with the contents of a matching `<turbo-frame>` element in the response. In practice, a `<turbo-frame>` element's contents are rendered as if they operated on by [`<turbo-stream action="update">`](/reference/streams#update) element. The underlying renderer extracts the contents of the `<turbo-frame>` in the response and uses them to replace the requesting `<turbo-frame>` element's contents. The `<turbo-frame>` element itself remains unchanged, save for the [`[src]`, `[busy]`, and `[complete]` attributes that Turbo Drive manages](/reference/frames#html-attributes) throughout the stages of the element's request-response lifecycle.

Applications can customize the `<turbo-frame>` rendering process by adding a `turbo:before-frame-render` event listener and overriding the `event.detail.render` property.

For example, you could merge the response `<turbo-frame>` element into the requesting `<turbo-frame>` element with [morphdom](https://github.com/patrick-steele-idem/morphdom):

```javascript
import morphdom from "morphdom"

addEventListener("turbo:before-frame-render", (event) => {
event.detail.render = (currentElement, newElement) => {
morphdom(currentElement, newElement)
}
})
```

Since `turbo:before-frame-render` events bubble up the document, you can override one `<turbo-frame>` element's rendering by attaching the event listener directly to the element, or override all `<turbo-frame>` elements' rendering by attaching the listener to the `document`.

## Pausing Rendering

Applications can pause rendering and make additional preparations before continuing.

Listen for the `turbo:before-frame-render` event to be notified when rendering is about to start, and pause it using `event.preventDefault()`. Once the preparation is done continue rendering by calling `event.detail.resume()`.

An example use case is adding exit animation:

```javascript
document.addEventListener("turbo:before-frame-render", async (event) => {
event.preventDefault()

await animateOut()

event.detail.resume()
})
```
34 changes: 34 additions & 0 deletions _source/handbook/04_streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,40 @@ Turbo Streams consciously restricts you to seven actions: append, prepend, (inse

Embracing these constraints will keep you from turning individual responses in a jumble of behaviors that cannot be reused and which make the app hard to follow. The key benefit from Turbo Streams is the ability to reuse templates for initial rendering of a page through all subsequent updates.

## Custom Actions

By default, Turbo Streams support [seven values for its `action` attribute](/reference/streams#the-seven-actions). If your application needs to support other behaviors, you can override the `event.detail.render` function.

For example, if you'd like to expand upon the seven actions to support `<turbo-stream>` elements with `[action="alert"]` or `[action="log"]`, you could declare a `turbo:before-stream-render` listener to provide custom behavior:

```javascript
addEventListener("turbo:before-stream-render", ((event) => {
const fallbackToDefaultActions = event.detail.render

event.detail.render = function (streamElement) {
if (streamElement.action == "alert") {
// ...
} else if (streamElement.action == "log") {
// ...
} else {
fallbackToDefaultActions(streamElement)
}
}
}))
```

In addition to listening for `turbo:before-stream-render` events, applications
can also declare actions as properties directly on `TurboStreamAction`:

```javascript
import { TurboStreamActions } from "@hotwired/turbo"

// <turbo-stream action="log" message="Hello, world"></turbo-stream>
//
TurboStreamActions.log = function (streamElement) {
console.log(streamElement.getAttribute("message"))
}
```

## Integration with Server-Side Frameworks

Expand Down
6 changes: 4 additions & 2 deletions _source/reference/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ Turbo emits events that allow you to track the navigation lifecycle and respond

* `turbo:before-cache` fires before Turbo saves the current page to cache.

* `turbo:before-render` fires before rendering the page. Access the new `<body>` element with `event.detail.newBody`. Rendering can be canceled and continued with `event.detail.resume` (see [Pausing Rendering](/handbook/drive#pausing-rendering)).
* `turbo:before-render` fires before rendering the page. Access the new `<body>` element with `event.detail.newBody`. Rendering can be canceled and continued with `event.detail.resume` (see [Pausing Rendering](/handbook/drive#pausing-rendering)). Customize how Turbo Drive renders the response by overriding the `event.detail.render` function (see [Custom Rendering](/handbook/drive#custom-rendering)).

* `turbo:before-stream-render` fires before rendering a Turbo Stream page update.
* `turbo:before-stream-render` fires before rendering a Turbo Stream page update. Access the new `<turbo-stream>` element with `event.detail.newStream`. Customize the element's behavior by overriding the `event.detail.render` function (see [Custom Actions](/handbook/streams#custom-actions)).

* `turbo:render` fires after Turbo renders the page. This event fires twice during an application visit to a cached location: once after rendering the cached version, and again after rendering the fresh version.

* `turbo:load` fires once after the initial page load, and again after every Turbo visit. Access visit timing metrics with the `event.detail.timing` object.

* `turbo:before-frame-render` fires before rendering the `<turbo-frame>` element. Access the new `<turbo-frame>` element with `event.detail.newFrame`. Rendering can be canceled and continued with `event.detail.resume` (see [Pausing Rendering](/handbook/frame#pausing-rendering)). Customize how Turbo Drive renders the response by overriding the `event.detail.render` function (see [Custom Rendering](/handbook/frames#custom-rendering)).

* `turbo:frame-render` fires right after `<turbo-frame>` element renders its view. The specific `<turbo-frame>` element is the event target. Access the `FetchResponse` object with `event.detail.fetchResponse` property.

* `turbo:frame-load` fires when `<turbo-frame>` element is navigated and finishes loading (fires after `turbo:frame-render`). The specific `<turbo-frame>` element is the event target.
Expand Down

0 comments on commit 4231925

Please sign in to comment.