Skip to content

Commit

Permalink
Add render to turbo:before-stream-render event
Browse files Browse the repository at this point in the history
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
  • Loading branch information
seanpdoyle committed Sep 12, 2022
1 parent 1c9ce3f commit 7465c4f
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 9 deletions.
16 changes: 12 additions & 4 deletions src/elements/stream_element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { StreamActions } from "../core/streams/stream_actions"
import { nextAnimationFrame } from "../util"

export type TurboBeforeStreamRenderEvent = CustomEvent<{ newStream: StreamElement }>
type Render = (currentElement: StreamElement) => Promise<void>

export type TurboBeforeStreamRenderEvent = CustomEvent<{ newStream: StreamElement; render: Render }>

// <turbo-stream action=replace target=id><template>...

Expand All @@ -26,6 +28,10 @@ export type TurboBeforeStreamRenderEvent = CustomEvent<{ newStream: StreamElemen
* </turbo-stream>
*/
export class StreamElement extends HTMLElement {
static async renderElement(newElement: StreamElement): Promise<void> {
await newElement.performAction()
}

async connectedCallback() {
try {
await this.render()
Expand All @@ -40,9 +46,11 @@ export class StreamElement extends HTMLElement {

async render() {
return (this.renderPromise ??= (async () => {
if (this.dispatchEvent(this.beforeRenderEvent)) {
const event = this.beforeRenderEvent

if (this.dispatchEvent(event)) {
await nextAnimationFrame()
this.performAction()
await event.detail.render(this)
}
})())
}
Expand Down Expand Up @@ -153,7 +161,7 @@ export class StreamElement extends HTMLElement {
return new CustomEvent("turbo:before-stream-render", {
bubbles: true,
cancelable: true,
detail: { newStream: this },
detail: { newStream: this, render: StreamElement.renderElement },
})
}

Expand Down
3 changes: 2 additions & 1 deletion src/tests/fixtures/stream.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html data-skip-event-details="turbo:submit-start turbo:submit-end">
<head>
<meta charset="utf-8">
<title>Turbo Streams</title>
Expand All @@ -10,6 +10,7 @@
<form id="append-target" method="post" action="/__turbo/messages">
<input type="hidden" name="content" value="Hello world!">
<input type="hidden" name="type" value="stream">
<input type="hidden" name="id" value="a-turbo-stream">
<button>Create</button>
</form>

Expand Down
19 changes: 16 additions & 3 deletions src/tests/functional/stream_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ test("test receiving a stream message", async ({ page }) => {

test("test dispatches a turbo:before-stream-render event", async ({ page }) => {
await page.click("#append-target button")
const { newStream } = await nextEventNamed(page, "turbo:before-stream-render")
await nextEventNamed(page, "turbo:submit-end")
const [[type, { newStream }, target]] = await readEventLogs(page, 1)

assert.equal(type, "turbo:before-stream-render")
assert.equal(target, "a-turbo-stream")
assert.ok(newStream.includes(`action="append"`))
assert.ok(newStream.includes(`target="messages"`))
})
Expand Down Expand Up @@ -73,9 +76,19 @@ test("test overriding with custom StreamActions", async ({ page }) => {
const html = "Rendered with Custom Action"

await page.evaluate((html) => {
window.Turbo.StreamActions.customUpdate = function () {
for (const target of this.targetElements) target.innerHTML = html
const CustomActions: Record<string, any> = {
customUpdate(newStream: { targetElements: HTMLElement[] }) {
for (const target of newStream.targetElements) target.innerHTML = html
},
}

addEventListener("turbo:before-stream-render", (({ target, detail }: CustomEvent) => {
const stream = target as unknown as { action: string }

const defaultRender = detail.render
detail.render = CustomActions[stream.action] || defaultRender
}) as EventListener)

window.Turbo.renderStreamMessage(`
<turbo-stream action="customUpdate" target="messages">
<template></template>
Expand Down
2 changes: 1 addition & 1 deletion src/tests/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ router.get("/stream-response", (request, response) => {
const { content, target, targets } = params
if (acceptsStreams(request)) {
response.type("text/vnd.turbo-stream.html; charset=utf-8")
response.send(targets ? renderMessageForTargets(content, targets) : renderMessage(content, target))
response.send(targets ? renderMessageForTargets(content, null, targets) : renderMessage(content, target))
} else {
response.sendStatus(422)
}
Expand Down

0 comments on commit 7465c4f

Please sign in to comment.