Comments (34)
@nickjj I just ran into this use case as well.
Iβm currently wiring up a list -> details view (consisting of a sidebar and a main content area) and Iβd love to change the URL from the index view /notes
to /note/:id
when the user clicks a note in the sidebar and itβs rendered into the details view (main content area).
I would be great if we could specify that a link with a data-turbo-frame
attribute should update the URL.
from turbo.
FWIW I accomplished it with the following stimulus controller using Mutation Observer.
https://gist.github.com/Intrepidd/ac68cb7dfd17d422374807efb6bf2f42
from turbo.
This issue doesn't mention, but there's easy standalone solution: data-turbo-action="advance"
from turbo.
Here's a reimplementation of my controller to reproduce this behaviour with turbo beta 4
Now that the Location API is being used rather than the previous internal API, we can use the turbo navigator and have a much smaller controller.
I'm also using stimulus-use to facilitate the mutation observer code, but you can mix and match with my previous implementation if you don't want to use stimulus-use.
https://gist.github.com/Intrepidd/bb1ffc5944a5c1ec3a9f5582753c4b67
from turbo.
What's the current state of this? I really like the idea of a more generic approach that's baked in already, similar to how we can opt-in/out of using turbo for individual links/blocks.
from turbo.
I would be great if we could specify that a link with a data-turbo-frame attribute should update the URL.
@bfitch I was thinking about this recently and I think that would be the way to go too. Because if you had a drop down menu loading in a frame you wouldn't want a URL change for that, so the URL push target likely needs to be an optional attribute.
Even in the hotwire example screencast it would be reasonable to want to set the URL when you edit a resource. This is seen at the 3 minute mark https://www.youtube.com/watch?t=180&v=eKY-QES1XQQ. Notice how the URL doesn't get updated when clicking edit, but a few seconds before when the content wasn't loading in a frame it did.
FWIW I accomplished it with the following stimulus controller using Mutation Observer.
@Intrepidd do you have a usage example to go with the controller? Or is it only a matter of attaching a src="/foo"
to the frame?
from turbo.
I would love this also. We have a, sort of, "wizard" on the main content part of the page. On the sidebar, we have a lazy-loaded frame. If we do nothing, each time we click through the wizard, the whole page reloads (including the lazy frame, which is annoying). We thought to add a frame around the wizard... which works, except that we want the option to change the URL on links inside that frame :).
Thanks!
from turbo.
Hi, the following workaround works pretty well for me:
document.addEventListener('turbo:frame-render', async e => {
if (['main', '_top'].includes(e.target.id))
history.pushState(history.state, '', await e.detail.fetchResponse.location)
})
window.addEventListener('popstate', () => Turbo.visit(document.location))
from turbo.
Hi all, I've successfully implemented @Intrepidd's Turbo Frames Navigation Controller in a Shopify embedded proxy app. So that's great and the whole principle work. Would be awesome to have this as some kind of data-attribute in Turbo itself.
One other thought I had. I basically have used this method now to embed a Turbo app inside another website. Essentially I only have the need for a single top level Turbo Frame. So another way of solving my use case would be to set a top level container other than body
with just Turbo Drive.
from turbo.
A gotcha with @Intrepidd solution:
It works perfectly if your server responds with the full page.
In my case, I tried to optimise and respond just with the turbo-frame if I detect the appropriate header set by Hotwire. For faster user experience, I also set a short expiration header:
expires_in 20.minutes
if request.headers["turbo-frame"] == "movie_feed"
render "movies_feed_turbo_frame"
else
render :index
end
Navigating into the frame works perfectly, but once you navigate to another page and hit the back button, Hotwire will replace the whole <body>
just with the contents of the cached <turbo-frame>
, as well as clear any classes previously set on the <body>
.
Even if you optimisation is simpler than mine (e.g. just layout: false
), Hotwire will clear out any classes you might have on the <body>
when you navigate back.
This doesn't happen if you don't set expiration headers of course.
Agree with @janko it would be great to have the whole use case supported by Turbo out of the box, as it allows you to efficiently patch the page with smaller updates while updating user's history for greater usability.
Example use case: a feed with filters that just updates the feed results and URL, while keeping the rest of the page untouched as you apply various filters.
from turbo.
@dhh Is there a plan on solving these url and history issues out of the box? To me the idea of providing a parameters to the link or form to define if it should replace the url and/or push to history sound promissing. Would be great to know your plans on this. So we know if we should build our own workaround or wait (and if needed/wanted help) for an implementation in turbo.
from turbo.
Without built-in support, is it possible to achieve this behavior without using a turbo-frame at all by using Turbo events:
https://turbo.hotwire.dev/reference/events
let mainFrame = null
addEventListener("turbo:click", ({ target }) => {
// [data-main-frame] is an arbitrary attribute name
mainFrame = target.closest("[data-main-frame][id]")
})
addEventListener("turbo:before-render", ({ detail }) => {
if (mainFrame?.id) {
const newMainFrame = detail.newBody.querySelector("#" + mainFrame.id)
if (newMainFrame) {
mainFrame.innerHTML = newMainFrame.innerHTML // replace the NEW frame's HTML with the OLD frame's HTML
detail.newBody.innerHTML = document.body.innerHTML // replace the NEW page's HTML with the OLD page's HTML
}
}
mainFrame = null
})
This will carry forward the rest of the page's HTML into a new Visit, while preserving the NEW content served within the "main frame".
This is pseudo code, but the concept itself might be viable.
from turbo.
@michelson you're right, thanks for the reporting. I'll update my gist with the fix soon
from turbo.
Considering the example of a video player whose playback is uninterrupted during navigation:
Would marking the <iframe>
or an ancestor with an [id]
attribute and data-turbo-permanent
help? If the player were permanent, the "tabs" wouldn't need to be frames, and could be full-on page navigations, and Turbo would manage the extraction and injection of the player element across visitations.
from turbo.
Would marking the <iframe> or an ancestor with an [id] attribute and data-turbo-permanent help?
It doesn't. The iframe visibly refreshes in the dom and the video loses its progress.
The end result is the same with the following 3 combinations when using drive without frames:
data-turbo-permanent
on the iframedata-turbo-permanent
and putting anid="helloworlduniqueid"
on the iframedata-turbo-permanent
and putting anid="helloworlduniqueid"
on a div that surrounds the iframe instead of the iframe
This behavior also happens with Turbolinks 5 btw. I think it's just related to the spec of iframes. There's a 17 year old bug report on this at https://bugzilla.mozilla.org/show_bug.cgi?id=254144. It's marked invalid due to the nature of iframes.
from turbo.
I would also be interested in having Turbo Frames work similar to the "Target" feature of htmx or unpoly .
With Turbo always replacing the complete body the "illusion" of using a SPA will sometimes be lost, especially if there are images on a page that do not change between sites but will flicker due to Turbo replacing the HTML.
from turbo.
from turbo.
Maybe we could have a concept of "main frame" or "navigation frame" that would be behaving like target _top url-wise but would not refresh the whole page
from turbo.
I think having the ability to mark a Frame as main
would solve the issue indeed: a unique frame in the page that sync its URL with the top URL. I don't think a navigation
one is needed with that idea as every other frame except the main one would behave as they already do.
from turbo.
I was also able to get the @Intrepidd Turbo Frames Navigation Controller working. It works nicely to push/pop state. The challenge for me is when I load into a targeted Turbo frame (e.g. Frame A) content from url 1 and then content from url 2, hitting back should load url 1 into Frame A, not load the url as an entire page load. We also need a way to maintain knowledge of what the original target was and then load into that frame. I could capture the frame id in the event state, but don't have a way to target the url into the frame.
from turbo.
This is pseudo code, but the concept itself might be viable.
Would you mind giving it a whirl with an iframe
on the page (such as embedding a YouTube video)? Only asking because I'm not quite sure how to implement your pseudo code into a working example.
from turbo.
@Intrepidd Works like a charm, thank you! ππ»
It would definitely be useful if this was part of Turbo, as I wouldn't be able to figure it out by myself.
from turbo.
I believe the best way to fix the back button problem with @Intrepidd's controller lies in this line:
if (src != null) { navigator.history.push(new URL(src)) }
We should push not only the URL to the state but the frame as well. And Turbo should respect that param as well as respects the data-turbo-frame attribute on links and buttons
Having something like this would be cool:
if (src != null) { navigator.history.push(new URL(src), { turboFrame: this.element.id }) }
from turbo.
I tried to hack an updated controller that handles history itself, and have the Back button working: https://gist.github.com/Kukunin/5033345db6da9d2edc002dc3f39702ac.
I was surprised how isolated turbo frames are implemented in Turbo, there is no way to pass turbo-frame to Turbo.visit nor to Navigator, and how turbo-frame works is via LinkInterceptor that just changes frame's src attribute.
from turbo.
The solution from @seanpdoyle is interesting too. I believe it won't work because of this line: detail.newBody.innerHTML = document.body.innerHTML
which will not only reload but rebuild iframes. But I wonder if we can do detail.newBody = document.body
and have Turbo not replace the body.
Just checked, document.body.replaceWith(document.body)
reloads iframes so replacement body with itself still won't fit. There should be a way to tell Turbo to not replace the body at all
from turbo.
+1 this feature would be invaluable
from turbo.
@Kukunin, I've tried your solution, and it works ok, but just for one history back, if I try to back twice, it does not change the page.
from turbo.
@michelson found a bug, where the observer pushed a new state every time the back button clicked. Fixed in my gist
from turbo.
@Kukunin thanks for let me know, I can confirm that is working flawlessly :)
from turbo.
@Kukunin it seems that the solution has an issue when navigating from history.
to reproduce:
- navigate from a page with a history-frame controller, then back to a page without the history frame or with a different frame
result:
- it changes the browser but it does not refresh or update the page.
I think it should make a navigator.history.push as a fallback , what you think ?
from turbo.
@michelson oh, that might be complicated since the controller needs to mount/demount itself.
It's out of the scope of my usage (I have the frame on every page), but I could take a look if you can create a reproducible case for me (as a Github repo).
Thank you for testing, it's valuable for community
from turbo.
Hi @Kukunin, thanks for taking your time to take a look, here is the example repo https://github.com/michelson/history-turbo-rails-example, let me know if that's what you need, I hope you find it useful to debug the problem.
demo:
Screen.Cast.2021-07-07.at.7.38.09.PM.mp4
I've tried the following, kinda works, but after the Turbo.visit
the history is gone
popStateListener(event) {
if ( event.state.turbo_frame_history){
if(event.state.turbo_frame === this.element.id){
this.element.src = window.location.href;
}else{
console.log("HISTORY NOT HANDLED FALLBACK REQUEST TO TURBO VISIT")
window.Turbo.visit(window.location.href)
}
}
}
from turbo.
I'd like to see Turbo offer more direct controls, but there's not a final proposal ready to go. There won't be anything in the box for 7.0.
So I'd say go right ahead and experiment in your own apps. That's a better way to figure out what an official version should look like anyway.
from turbo.
@xpopov data-turbo-action="advance" does solves the forward navigation issue, but its showing weird behavior when I click back button in the browser. I am new to the rails ecosystem, honestly these little things are pretty annoying and it's really hard to figure out what's happening. I wish these would work out of box @dhh just like rails.
from turbo.
Related Issues (20)
- Script tags are reexecuted when navigating back from a page with an error status code using browser's back button HOT 1
- Eager-loaded frame fetches but does not render its src HOT 1
- Server-side redirect from turbo stream to file download fails in Turbo 8
- Firefox issue with `scroll-behavior: smooth` and Turbo enabled (v8.0.4) HOT 3
- The documentation does not mention that Stream Action tags can be rendered inside any HTML HOT 5
- using data-turbo-action "replace" or "advance" cause turbo:before-cache event to be called on live page HOT 1
- Occasional disconnect, no documented way to monitor or reconnect HOT 5
- Incorrect page content when a Turbo "replace" stream action is followed by a refresh
- turbo-confirm doesn't work on links without a turbo-method HOT 1
- Window.fetch has been overwritten by Stimulus HOT 5
- Adding stimulus breaks a test...why?
- Indefinite Progress Bar on Turbo.visit with turbo-stream content HOT 5
- Preserve scroll position when morphing turbo frames
- turbo:morph-element fires twice
- Form not displaying errors on page HOT 2
- Morph with infinite scroll
- turbo-frame with text only not rendering on restoration visits
- How to prevent element from being removed during morph? HOT 3
- data-turbo="false" misbehaviour
- Drive: Navigating back from a 3xx redirect to an external page makes turbo show 'loading' forever
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from turbo.