Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions src/core/drive/morphing_page_renderer.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import { PageRenderer } from "./page_renderer"
import { dispatch } from "../../util"
import { morphElements, shouldRefreshFrameWithMorphing, closestFrameReloadableWithMorphing } from "../morphing"
import { morphBodyElements } from "../morphing"

export class MorphingPageRenderer extends PageRenderer {
static renderElement(currentElement, newElement) {
morphElements(currentElement, newElement, {
callbacks: {
beforeNodeMorphed: (node, newNode) => {
if (
shouldRefreshFrameWithMorphing(node, newNode) &&
!closestFrameReloadableWithMorphing(node)
) {
node.reload()
return false
}
return true
}
}
})

dispatch("turbo:morph", { detail: { currentElement, newElement } })
static render(currentBody, newBody) {
morphBodyElements(currentBody, newBody)
}

async preservingPermanentElements(callback) {
Expand All @@ -34,4 +18,3 @@ export class MorphingPageRenderer extends PageRenderer {
return false
}
}

26 changes: 3 additions & 23 deletions src/core/frames/morphing_frame_renderer.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
import { FrameRenderer } from "./frame_renderer"
import { morphChildren, shouldRefreshFrameWithMorphing, closestFrameReloadableWithMorphing } from "../morphing"
import { dispatch } from "../../util"
import { morphTurboFrameElements } from "../morphing"

export class MorphingFrameRenderer extends FrameRenderer {
static renderElement(currentElement, newElement) {
dispatch("turbo:before-frame-morph", {
target: currentElement,
detail: { currentElement, newElement }
})

morphChildren(currentElement, newElement, {
callbacks: {
beforeNodeMorphed: (node, newNode) => {
if (
shouldRefreshFrameWithMorphing(node, newNode) &&
closestFrameReloadableWithMorphing(node) === currentElement
) {
node.reload()
return false
}
return true
}
}
})
static render(currentFrame, newFrame) {
morphTurboFrameElements(currentFrame, newFrame)
}

async preservingPermanentElements(callback) {
return await callback()
}
}

30 changes: 1 addition & 29 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { PageSnapshot } from "./drive/page_snapshot"
import { FrameRenderer } from "./frames/frame_renderer"
import { fetch, recentRequests } from "../http/fetch"
import { config } from "./config"
import { MorphingPageRenderer } from "./drive/morphing_page_renderer"
import { MorphingFrameRenderer } from "./frames/morphing_frame_renderer"

export { morphChildren, morphElements } from "./morphing"
export { morphChildren, morphElements, morphBodyElements, morphTurboFrameElements } from "./morphing"

const session = new Session(recentRequests)
const { cache, navigator } = session
Expand Down Expand Up @@ -120,29 +118,3 @@ export function setFormMode(mode) {
)
config.forms.mode = mode
}

/**
* Morph the state of the currentBody based on the attributes and contents of
* the newBody. Morphing body elements may dispatch turbo:morph,
* turbo:before-morph-element, turbo:before-morph-attribute, and
* turbo:morph-element events.
*
* @param currentBody HTMLBodyElement destination of morphing changes
* @param newBody HTMLBodyElement source of morphing changes
*/
export function morphBodyElements(currentBody, newBody) {
MorphingPageRenderer.renderElement(currentBody, newBody)
}

/**
* Morph the child elements of the currentFrame based on the child elements of
* the newFrame. Morphing turbo-frame elements may dispatch turbo:before-frame-morph,
* turbo:before-morph-element, turbo:before-morph-attribute, and
* turbo:morph-element events.
*
* @param currentFrame FrameElement destination of morphing children changes
* @param newFrame FrameElement source of morphing children changes
*/
export function morphTurboFrameElements(currentFrame, newFrame) {
MorphingFrameRenderer.renderElement(currentFrame, newFrame)
}
59 changes: 59 additions & 0 deletions src/core/morphing.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,65 @@ export function morphChildren(currentElement, newElement, options = {}) {
})
}

/**
* Morph the state of the currentBody based on the attributes and contents of
* the newBody. Morphing body elements may dispatch turbo:morph,
* turbo:before-morph-element, turbo:before-morph-attribute, and
* turbo:morph-element events.
*
* @param currentBody HTMLBodyElement destination of morphing changes
* @param newBody HTMLBodyElement source of morphing changes
*/
export function morphBodyElements(currentElement, newElement) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% convinced about this name. You might want to use this method in something like turbo:before-render to replace something like div#app instead of body. But it's true that this event also gives you newBody as a parameter. I feel like the difference between morphBodyElements and morphElements is very subtle. One is simply lower level than the other, but we expose them at the same level.

What do you think if we rename this to just morph, so it becomes Turbo.morph(currentElement, newElement, { action: "replace|update", refreshFrames: true|false }), and leave morphElements as private? It'd be something similar to Turbo.visit(url, { frame: "id" }). Also, that way, we'd always dispatch the turbo:morph event. Then the morph method of the Turbo stream actions could also use that method. Perhaps the names of the option params could be improved, but they come pretty close to the idea of Turbo stream actions.

morphElements(currentElement, newElement, {
callbacks: {
beforeNodeMorphed: (node, newNode) => {
if (
shouldRefreshFrameWithMorphing(node, newNode) &&
!closestFrameReloadableWithMorphing(node)
) {
node.reload()
return false
}
return true
}
}
})

dispatch("turbo:morph", { detail: { currentElement, newElement } })
}

/**
* Morph the child elements of the currentFrame based on the child elements of
* the newFrame. Morphing turbo-frame elements may dispatch turbo:before-frame-morph,
* turbo:before-morph-element, turbo:before-morph-attribute, and
* turbo:morph-element events.
*
* @param currentFrame FrameElement destination of morphing children changes
* @param newFrame FrameElement source of morphing children changes
*/
export function morphTurboFrameElements(currentElement, newElement) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding turboFrame.morphWith(newElement) instead? It feels more similar to element.replaceWith(newElement).

dispatch("turbo:before-frame-morph", {
target: currentElement,
detail: { currentElement, newElement }
})

morphChildren(currentElement, newElement, {
callbacks: {
beforeNodeMorphed: (node, newNode) => {
if (
shouldRefreshFrameWithMorphing(node, newNode) &&
closestFrameReloadableWithMorphing(node) === currentElement
) {
node.reload()
return false
}
return true
}
}
})
}

export function shouldRefreshFrameWithMorphing(currentFrame, newFrame) {
return currentFrame instanceof FrameElement &&
// newFrame cannot yet be an instance of FrameElement because custom
Expand Down
Loading