hotwired / turbo

The speed of a single-page web application without having to write any JavaScript
https://turbo.hotwired.dev
MIT License
6.54k stars 415 forks source link

Re-structure `turbo-stream[action=morph]` support #1240

Open seanpdoyle opened 2 months ago

seanpdoyle commented 2 months ago

Closes https://github.com/hotwired/turbo/issues/1163 Follow-up to https://github.com/hotwired/turbo/pull/1185

turbo-site PR

This commit re-structures the new support for turbo-stream[action="morph"] elements introduced in #1185.

Since the <turbo-stream action="morph"> hasn't yet been part of a release, there's an opportunity to rename it without being considerate of backwards compatibility.

As an alternative to introduce a new Stream Action, this commit changes existing actions to be more flexible.

For example, the <turbo-stream method="morph"> element behaves like a specialized version of a <turbo-stream method="replace">, since it operates on the target element's outerHTML property.

Similarly, the <turbo-stream method="morph" children-only> element behaves like a specialized version of a <turbo-stream method="update">, since it operates on the target element's innerHTML property.

-<turbo-stream action="morph">
+<turbo-stream action="replace" method="morph">
   <template>Replace me with morphing</template>
 </turbo-stream>

-<turbo-stream action="morph" children-only>
+<turbo-stream action="update" method="morph">
   <template>Update me with morphing</template>
 </turbo-stream>

This commit removes the [action="morph"] support entirely, and re-implements it in terms of the [action="replace"] and [action="update"] support.

By consolidating concepts, the "scope" of the modifications is more clearly communicated to callers that are familiar with the underlying DOM interfaces (Element.replaceWith and Element.innerHTML) that are invoked by the conventionally established Replace and Update actions.

This proposal also aims to reinforce the "method" terminology introduced by the Page Refresh <meta name="turbo-refresh-method" content="morph"> element.

seanpdoyle commented 2 months ago

I started to open a PR related to https://github.com/hotwired/turbo-rails/pull/583 to remove turbo_stream.morph with extension to its turbo_stream.replace and turbo_stream.update counterparts, but wasn't sure whether or not it'd be worth removing those methods.

For example, the turbo_stream.morph method could serve as an abstraction on top of building a <turbo-stream action="replace" method="morph"> in the same way as a turbo_stream.morph(..., children_only: true) call could be an abstraction on top of building a <turbo-stream action="update" method="morph">.

If this PR's proposal is well-received, I'll open a corresponding PR to turbo-rails.

seanpdoyle commented 2 months ago

After doing some more research, I see that the original implementation introduced in https://github.com/hotwired/turbo/pull/1185 intentionally overlaps with the turbo-stream[action="morph"] element provided by the turbo-morph package.

Since that package integrates with morphdom, support for the [children-only] attribute is a natural corollary to the childrenOnly: Boolean option that Morphdom supports.

Since Turbo 8's morphing is powered by Idiomorph, drawing inspiration from the Morphdom API isn't as compelling as it is for the turbo-morph package.

Instead, utilizing existing Turbo terminology (instead of something like [morph-style] to mirror Idiomorph's morphStyle: "innerHTML" | "outerHTML" option) encapsulates that implementation detail in a way that makes it possible to change the underlying morphing library in the future without affecting downstream applications.

seanpdoyle commented 2 months ago

@jorgemanrubia does this feel like an improvement?

jonsgreen commented 1 month ago

Not sure that my 2 cents are of value here but I do like the idea of having a different api for stream morphing that would not conflict with the turbo-power library. There are other features in turbo-power that are of value but I would prefer to use turbo with Idiomorph for morphing on a page and I believe that if turbo_rails were to use turbo_stream.morph it would collide with turbo-power.

@seanpdoyle, I am curious if you have a sense of when this feature might be released? As noted in #1229 it is in the docs but not actually functional yet which tripped me up. As other folks have mentioned in #1185 I have a pattern that I use in an app in which I post back to the same action (e.g. new or edit) to update the state of an object but without persisting it to the database so a redirect is not possible. I would like the UI to adjust as the state of the form object changes without doing a full replace since that would lose focus so morphing would be ideal.

I think I did at least finally figure out how to get the full page render to trigger a morph refresh by passing a status code other than 200. Does that sound right? Tricky.