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

Track Turbo Frames Visits and Navigation History in the URL Hash #692

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

marcoroth
Copy link
Member

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 the advance 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.

    • I played around with using atob() and btoa(), 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
    • The downside of using the base64-econded version is that you can't link_to a predefined [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!

This change is in order to provide more meaningful history navigation
with the Browser Back/Forward buttons and to make sure that a navigated
frame stays at that URL if you decide to reload that page
@marcoroth marcoroth marked this pull request as draft August 17, 2022 20:46
@seanpdoyle
Copy link
Contributor

This is really interesting! Thank you for taking this on.

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.

What do you think about a syntax like Rails' attribute nesting?:

> const params = new URLSearchParams([["a_frame[src]", "/1.html"], ["b_frame[src]", "/2.html"]])
> params.toString()
< "a_frame%5Bsrc%5D=%2F1.html&b_frame%5Bsrc%5D=%2F2.html"
> const parsed = new URLSearchParams(params.toString())
> Array.from(parsed)
< [["a_frame[src]", "/1.html"], ["b_frame[src]", "/2.html"]] (2)

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.
...
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.

For me, this is the biggest blocker and unanswered question. Breaking that behavior by default is a non-starter. I wonder what the best way to strike that balance would be.

@marcoroth
Copy link
Member Author

What do you think about a syntax like Rails' attribute nesting?

Yeah, that's an interesting format too, especially if we would want to store more than just the src per frame.

For me, this is the biggest blocker and unanswered question. Breaking that behavior by default is a non-starter. I wonder what the best way to strike that balance would be.

I'm wondering if we could do a simple parsing of the URL hash on page load and detect that way if it contains a regular anchor. If it does, let it do the default behaviour of jumping to that anchor, otherwise try to process the (remaining) URL hash as we have in this PR.

Another approach could be to rely on regular URL params instead of the hash, but that also feels wrong. I'm going to investigate how other SPA frameworks handle this problem.

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

Successfully merging this pull request may close these issues.

2 participants