-
Notifications
You must be signed in to change notification settings - Fork 439
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
Replace two different turbo-frame from one response #56
Comments
See this commit to see how to do this hotwired/turbo-rails@5c49e57 |
Thanks @robzolkos I am not using ruby, but this looks to me like it is related to turbo-streams, returning different responses depending on the existence of a template, and not related to turbo-frames, but maybe I am wrong and not understanding the PR. What I want is to return 2 turbo-frames in the same response, not to return one or the other. Is that what the PR does and I am misunderstanding it? |
By the way, here is a discussion on the hotwire discuss, of someone trying to achieve the same thing: https://discuss.hotwire.dev/t/loading-two-frames-from-a-single-application-visit-event/1556 |
As far as I understand, frames are for scoped navigation. If you want to replace 2 parts of your page at once after a form submit (or from websocket) you need to use turbo streams to replace both those elements. If the only thing you do with those blocks is bulk replace them and they don't have their own navigation context, you don't even need to use frames, turbo streams just replace from DOM id. |
If you want to replace multiple frames, you can also just target _top and replace the whole thing via drive. But otherwise there won't be a path to custom replace two frames. When you need that, you gotta go to turbo streams. |
Yes, finally I am targeting _top to do it. The downside of this is that, as a redirect is needed to use _top, the whole page can make heavy operations (not my current case), and targeting _top would be unnecessarily slow because that parts of the page wouldn't change. Using streams could avoid that, but it feels a little bit overcomplicated to me just to replace two frames. That is why I thought that maybe there would be an easy way where two frames can be used in a form response. Thanks and good work! |
Hi! How about this solution?
|
@tothda fantastic! it works! In fact, it's even simpler. There is no need to redirect to another location that loads the streams. Just return a response from the form submitted with turbo-frame, with:
And all contents will be replaced without any kind of async calls. @dhh I think this is a common case (example: the user name changes in the navbar dropdown when the user updates his profile name). Maybe it makes sense to add a little note in the turbo-frame/turbo-stream doc? Do you think it's a good idea? Thanks to all! |
Thanks @tothda and @davidjr82! Small update: with Rails 7 I used content type "text/vnd.turbo-stream.html" and it worked perfectly! |
For new searchers, how To target multiple elements with a single action, use the targets attribute with a CSS query selector |
Here's another use case where this would be handy for GET requests using only frames. Imagine having:
When navigating to different videos using the table of contents, both the video frame and links frame would get updated. Using |
There can be an infinite number of scenarios where having a single turbo-frame generated request update multiple frames in its response is desirable. Using The scenario that brought me here is that we have a system where we allow access to edit the view templates but not the backend logic. There are workarounds to do things with turbo-streams but it would be much simpler to allow a view author to just list the elements to be replaced: What are the compelling reasons to not support this use case? |
Another solution to the problem could be to custom renderer. Use only one frame per page and exclude blocks you don't want to update. <html>
<head>
<title>Example</title>
</head>
<body>
<turbo-frame id="detail-page" data-turbo-action="advance" data-turbo-marphdom="true">
<div class="row">
<div class="col-12 col-lg-4 col-xl-3" data-morphdom-ignore>
Not update aside col
</div>
<div class="col-12 col-lg-8 col-xl-9">
<a href="/link" data-turbo="true">Link 1</a>
<a href="/link2" data-turbo="true">Link 2</a>
</div>
</div>
</turbo-frame>
</body>
</html> import morphdom from 'morphdom'
document.addEventListener('turbo:before-frame-render', (event) => {
const { target } = event;
if (!target.hasAttribute('data-turbo-morphdom')) {
return;
}
event.detail.render = (fromNode, toNode) => {
morphdom(fromNode, toNode, {
onBeforeElUpdated: (fromEl, toEl) => {
if (fromEl.isEqualNode(toEl)) {
return false
}
return !fromEl.hasAttribute('data-morphdom-ignore');
},
});
};
}); Sure, you can set up nested frames on the page in the same way. |
This may or may not solve the problem for you, but there is one more option that is not obvious nor seem to be documented. You can nest turbo streams inside turbo frame like this:
|
I'm struggling with this same issue of wanting to update two frames in response to a single click. My situation is much like @nickjj described with his video player example. Turbo 8 creates a possible solution to this problem. You can target _top and if rails determines this is a Page Refresh then it will use page morphing and smartly update only the parts of the page that changed, but there can be many separate parts sprinkled throughout the page within different frames. This new behavior is explained pretty well in this article: https://jonsully.net/blog/turbo-8-page-refreshes-morphing-explained-at-length But rails only does a Page Refresh morph when you have a route such as /videos and clicking a button POSTs to a route such as /videos/123 then redirects back to /videos. But if you instead have a route such as /videos which has a link that does a GET that targets _top, rails decides to do a full page Turbo Drive refresh instead. I "solved" this issue by turning my link into a POST which then does a redirect back to my original URL, but this does not feel like the right solution. It feels dirty and hacky and caused me to introduce a new route. I'm still investigating if the new Page Refresh action can be explicitly invoked rather than implicitly being triggered by comparing the URLs. It feels like we should just be able to put |
Thanks @krschacht. I'm not sure if Turbo 8 will have a solution in the end because the page morphing happens client side right? If that's the case then the expensive queries and view rendering will happen server side to generate the full HTML payload to send back to the client where it will be diff'd. This includes expensive table of contents rendering, questions and answers and other content that could exist on the page in addition to the video frame. Using individual frames and streams allows you to bypass having to render those other expensive areas of the page because on the server it will only render what needs to be rendered in the frame or stream (in this case the video player). Unless I'm drastically misunderstanding how Turbo 8 works? It's one of those things where I really want to use it to simplify everything but I'm not sure I can due to the efficiency loss. |
@nickjj It does still render server side and morph on the client, but when you follow the full Rails Turbo playbook, I think this isn't an issue in practice. At the end of the day, full page refreshes always end up fast through two techniques: caching and decomposition through frames. (1) There is such a heavy incentive to make the full page render fast and rails caching provides multiple ways to address this. So if the concern over morphing is that the full page render is expensive then your issue is likely elsewhere—we generally need to make the full page render fast, regardless. (2) In the cases where there is still a challenge on full page load, you throw the offending section into a turbo frame. Not only does this facilitate caching, but you can then load the frame in parallel/async so that the rest of the page doesn't block on it. You can also put a data-turbo-permanent and/or refresh=morph on your frame. These two additions to your frame are golden. First, you trigger a full page reload and the I'm still piecing all this together, but overall Turbo 8 does bring the right new tools for replacing multiple turbo-frames in one response. I'm still just trying to figure out how to trigger full page morphs without having to redirect back to the original page. |
Situation:
Question:
Great package. Thanks!
The text was updated successfully, but these errors were encountered: