Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add morph Turbo Stream Action #1163

Closed
danecando opened this issue Feb 5, 2024 · 14 comments · May be fixed by #1240
Closed

Add morph Turbo Stream Action #1163

danecando opened this issue Feb 5, 2024 · 14 comments · May be fixed by #1240

Comments

@danecando
Copy link

danecando commented Feb 5, 2024

Would be nice to support a morph turbo stream action for users who maybe aren't ready to opt in or transition to full page morphing.

Turbo.StreamActions.morph = function() {
  this.targetElements.forEach((target) => {
    Idiomorph.morph(target, this.templateContent)
  })
}
@krschacht
Copy link

@danecando what would be an example use case for this? In my usage so far, you either use Turbo Stream -or- you morph. I can’t imagine using a stream to trigger a morph.

When I’m doing a turbo stream, it’s because I’m updating the specific piece of the page that I care about. Are you using Turbo Stream for a large section?

@danecando
Copy link
Author

danecando commented Feb 7, 2024

I think the use cases are probably less than I had suspected initially. Probably replacing larger chunks of content or things containing images to prevent additional requests or potential flashes

@adrienpoly
Copy link
Member

I had a use case when I wanted to animate a list of item using https://auto-animate.formkit.com/
When an action creates a new item I could not just append it to the list as the list was sorted. So I decided to render the full list back as a stream. A standard update would not play the animation. Using a morph stream only the new item got appended to the list at the correct location and with the animation. They are probably other ways to do this with lifecycle event but here that made the solution pretty straight forward.

@krschacht
Copy link

krschacht commented Feb 7, 2024

@adrienpoly Are you suggesting that there is already a turbo stream action for triggering a morph? Or do you mean that you chose to not use a turbo stream action and instead triggered a page refresh / morph of the whole page?

@krschacht
Copy link

krschacht commented Feb 7, 2024

Overall, I don’t think it’s right to conceive of morph as desired turbo stream action. I’d suggest we probably close this as an issue.

Page Refresh w/ morph and Turbo Stream actions are really alternatives to each other. We are intended to pick one or the other.

If you want to be surgical about your updates to the page, you wire up a bunch of Turbo Stream actions which makes just the right update/append/insert as needed. Or if that’s too much work, you can instead trigger a full Page Refresh and tell it to morph what it gets back. It’s a little heavier handed (e.g. two HTTP responses and a larger payload) but not too much so and it should just magically work.

@adrienpoly
Copy link
Member

@krschacht no there is no official Morph but adding a custom action is fairly easy now. https://marcoroth.dev/posts/guide-to-custom-turbo-stream-actions. They are even a few packages to get it out of the box

I was just giving a use case where Stream morph has been useful for me in the past. Maybe today I would build it with a full page morph 🤷‍♂️

@krschacht
Copy link

krschacht commented Feb 7, 2024

@adrienpoly Ah, yes, that’s not crazy. I think this is a great example of why there i support for custom actions.

I’d say: custom actions is the right “solution” the library provides for this need and adding it as an official turbo stream action wouldn’t be right. This is a case where the library making this “difficult” is a feature rather than a bug.:)

@marcoroth
Copy link
Member

There are definitely reasons why you'd want to have an independent morph action. Morphing has the benefit of not loosing client-side state. In any case where you are currently using turbo_stream.replace or turbo_stream.update you could consider upgrading that call to a turbo_stream.morph. I've personally done this quite often.

But even if we don't add a morph action into the official package, there is turbo_power or the stripped down version turbo-morph that provide a morph action out of the box.

@danecando
Copy link
Author

@krschacht no there is no official Morph but adding a custom action is fairly easy now. https://marcoroth.dev/posts/guide-to-custom-turbo-stream-actions. They are even a few packages to get it out of the box

Downside being the user would have to load the Idiomorph dependency twice since it's not exposed in the Turbo bundle - unless I'm mistaken?

@m4theushw
Copy link

I'm working on a project where I'd to create a morph action to update the content of a modal. It works as follow. First, the user navigates to a page containing a list of foods, then there's an "Add Food" link that returns an append action adding the modal element to the page. Inside this modal, the user can search and choose one or more foods to add to the list. Each time the search form is submitted, it returns a morph action updating only the content of the modal. The modal is already wrapped in a Turbo Frame but I still opted for morphing to preserve the already selected foods and avoid the focus jumping to the search input, because of autofocus, whenever the user makes another search. The selected foods are stored as input[type=hidden] elements that I programmatically add or remove from the DOM. Before that, I checked https://github.com/marcoroth/turbo_power but it uses morphdom and I didn't want to keep two different morphing libraries.

Downside being the user would have to load the Idiomorph dependency twice since it's not exposed in the Turbo bundle - unless I'm mistaken?

I think idiomorph should be a peer-dependency or exposed so we can reuse it elsewhere.

@ksweetie
Copy link

ksweetie commented Feb 8, 2024

FWIW, we have a use case similar to @m4theushw and would make heavy use of turbo stream morphs. We use a lot of modals, especially on expensive pages, where the modal will load a lightweight form. When making selections in the form, we often want to re-render it, since certain selected fields might change other parts of the form. We don't want to reload the whole page, because the modal is sitting on top of an expensive page, and when a form changes, you want it to re-render as quickly as possible. innerHTML is also a bad use case for re-rendering a form more generally, because the focused input will change, which is disorienting for the user.

Stimulus Reflex has a similar distinction, where you can do either a full page morph or a selector morph. But with that library, you can use either morphdom or innerHTML for both of those use cases. That makes more sense to me. Sometimes I want to completely blow away the previous DOM and replace it with a new tree. Other times I want to merge in changes but keep the client-side state, event listeners, and element focus. But I don't think about the difference in those two use cases being the difference in replacing the whole page vs part of a page.

I recently spiked out adding morphdom as a custom action (e.g. turbo_stream.morph), and it works fine. But just wanted to add another voice saying that would be a useful first-class feature.

@omarluq
Copy link
Contributor

omarluq commented Feb 15, 2024

I added a PR to address this. For me the main motivation is being able to morph a component/element on the page while preserving the state. That can be handy in so many use cases.

@omarluq
Copy link
Contributor

omarluq commented Apr 5, 2024

@seanpdoyle I think this issue can be closed 🎉

@seanpdoyle
Copy link
Contributor

@afcapel or @jorgemanrubia could either of you close this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

9 participants