Track Turbo Frames Visits and Navigation History in the URL Hash #692
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
First up: This PR is WIP and just serves as a proof-of-concept. I've been thinking quite some time about it and came to the conclusion that there are a bunch of things which need to be addressed and discussed first before "finishing" the implemention. That's why I'm opening this PR so we have a foundation and place to discuss things.
Motivation
Currently Turbo Frame navigation visits within a frame don't push anything to the history object. Unless you add a
[data-turbo-action="advance"]
attribute to the<turbo-frame>
, which also has the side-effect that the frame gets promoted. If you decide to reload the page after the frame got promoted you won't see the same page as you did before. Because it promoted the source of that frame it will also load the whole content of said page instead of just the content of the matching<turbo-frame>
you saw before.That leaves the question: How do you update the history object if for navigation visits within the frame?
This Pull Request adds the functionality that Turbo Frames push their state/src to the URL hash in order to track the navigation history of the different frames on a page.
This has the benefit that you can also use the browser back- and forward-buttons to cycle through the frame navigations, while keeping the frames and their surroundings in-place.
Since every frame navigation gets pushed to the URL Hash you can reload the page at any given point in time and don't loose the "state" of the frames on the page, because the
[src]
is now being tracked as part of the URL. This also allows to have the history of all frames on the page vs. just the one that got promoted with theadvance
action.Because of the underlaying structure you can also override a frames's
[src]
by specifying the source in the URL hash which allows you to pre-define a frames[src]
in the URL, either from the client- or server-side.For certain applications this can make a huge difference in the UX because all link-click navigations within frames are now part of the history. To the users it's not transparent and obvious on which it works the history works and on which it doesn't.
Current Implementation
The current implementation "just" works for the current use-case. There are also some rough spots and edge-cases which need rework, hence the WIP.
As-is, it also breaks backwards compability, since I changed the current Turbo "defaults and assumptions". So it's excepted that some of the tests fail. Though I listed the options on how to address those as open points in the section below.
Additionally I feel like @seanpdoyle's open PR #657 could also simplify this and the future implementation of such a feature quite a bit.
Demo
Live-Demo:
https://turbo-frames-url-hash.netlify.app
I prepared a small demo for this functionality. By default both frames have no
[src]
attribute specified, which means they just display the content they have specified within their<turbo-frame>
tag.Once you clicked and navigated the frames using the links within the frames a few times you can cycle through the history and the different "states" of the frames using the brower's back- and forward-buttons.
Screen.Recording.2022-08-17.at.16.13.25.mov
If you visit the demo via the URL below it will automatically navigate the second frame to the URL specified in the URL hash on page load:
https://turbo-frames-url-hash.netlify.app/#frame_two_frame_src=%2Fsrc%2Fyellow.html
Open Points
I feel like an opt-in for this behaviour using something like
[data-turbo-action="hash"]
or[data-turbo-action="urlhash"]
would make sense. Since not everyone might want this enabled by default. It seems that the sane way is to have it turned off by default. Even if there would be a way which wouldn't break backwards-compatibility.Somehow it feels like that some parts of this idea are clashing with the idea of having
[data-turbo-action="advance"]
on a<turbo-frames>
. Could we somehow unify them or is it worth keep them separate?Currently a back/forward navigation fetches the content of the frame source again. When should a fetch happen vs. when should we use a (frame) snapshot to restore the content. Should this also be configurable?
If the URL hash specifies a
[src]
for a frame it will override whichever src was present in the actual page HTML on first load. Are there are any unintended side-effects because of that?A lot of web-apps currently rely on anchors to scroll/jump to a position on the page. Somehow we need to find a way to incorporate that transparently.
The URL hash format and the key for the frame source in the URL are currently like:
#${id}_frame_src=${frame_src}
. There might be the possibilty to shrink the key to just${id}_src
or even just${id}
, but that might clash with other anchors.If a
<turbo-frame>
has content between it's tags it will render that out. If the same frame also has a src in the URL Hash specified it will replace the previous content after fetching it. But because the source can take some time to be fetched it leads to a quick content flash. There might be a way to prevent the initial render if a src is present in the hash.How should we handle nested frames? Should they even be supported?
The URL hash can look imitating and clutters up the address bar quite a bit, but there isn't really a good way around it.
atob()
andbtoa()
, which makes the URL look like:http://localhost:5173/#ZnJhbWVfdHdvX2ZyYW1lX3NyYz0lMkZzcmMlMkZ5ZWxsb3cuaHRtbCZmcmFtZV9vbmVfZnJhbWVfc3JjPSUyRnNyYyUyRnJlZC5odG1s
vs.http://localhost:5173/#frame_two_frame_src=%2Fsrc%2Fyellow.html&frame_one_frame_src=%2Fsrc%2Fred.html
[src]
from the server-side without having to encode it first too, so it's probably better to go with the regular url-encoded version.Feedback
If you have any kind of feedback about the functionality or ideas explained here please let me know by commenting on this PR. Any thought or idea is super valued and appreciated!