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

Push state and updating URLs with Turbo Frames #50

Closed
nickjj opened this issue Dec 28, 2020 · 34 comments · Fixed by #398
Closed

Push state and updating URLs with Turbo Frames #50

nickjj opened this issue Dec 28, 2020 · 34 comments · Fixed by #398

Comments

@nickjj
Copy link

nickjj commented Dec 28, 2020

Hi,

Let's say I wanted tabs on a page to be loaded in frames but I wanted each tab to have its own distinct URL. Currently it doesn't seem like this is possible out of the box because switching between frames doesn't update the URL.

Here's a real world use case of where I wanted to use Turbo Frames but ended up aborting the idea of it and going back to making a raw ajax request and then not rendering the layout of the page at the controller level when I detected it was an ajax request.

We all remember Railscasts right? Here's one of its pages: http://railscasts.com/episodes/416-form-objects?autoplay=true

The neat thing about this page is if you're playing a video, you can navigate between the show notes, comments and the other tabs without the video player stopping. Each tab also has its own unique URL so you can bookmark it and share it with others.

In Railscast's case I believe just using Turbo Drive and the permanent attribute would have been good enough to make this work because Ryan is using a native video player.

However, a lot of video services like Vimeo expect you to embed an iframe and iframes have a very unique characteristic in that as part of the spec if the underlying DOM of the iframe changes then the iframe gets reloaded which means the video watching experience gets interrupted (even when using the permanent attribute).

So that leaves us with at least 2 choices to get an uninterrupted video playback experience:

  1. Have each tab as its own controller but then manually set up some JS to make each link an ajax request and have Rails not render the layout at the controller level unless someone is directly accessing the URL.

  2. Use the new Turbo Frames feature (which works), but then the URL doesn't get updated.

Is there a way forward where push state could be added to Turbo Frames, or would that not make sense for the use case that frames is trying to tackle, and in the above case we should wire up a manual ajax call to have that desired effect?

@seanpdoyle
Copy link
Contributor

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.

@nickjj
Copy link
Author

nickjj commented Dec 29, 2020

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 iframe
  • data-turbo-permanent and putting an id="helloworlduniqueid" on the iframe
  • data-turbo-permanent and putting an id="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.

@bfitch
Copy link

bfitch commented Dec 30, 2020

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

@timmhirsens
Copy link

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.

@Intrepidd
Copy link
Contributor

FWIW I accomplished it with the following stimulus controller using Mutation Observer.

https://gist.github.com/Intrepidd/ac68cb7dfd17d422374807efb6bf2f42

@nickjj
Copy link
Author

nickjj commented Dec 30, 2020

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?

@Intrepidd
Copy link
Contributor

Intrepidd commented Dec 30, 2020 via email

@michiels
Copy link

michiels commented Jan 9, 2021

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.

@weaverryan
Copy link

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!

@Intrepidd
Copy link
Contributor

Intrepidd commented Jan 20, 2021

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

@tgalopin
Copy link

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.

@wlmeurer
Copy link

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.

@seanpdoyle
Copy link
Contributor

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.

@nickjj
Copy link
Author

nickjj commented Jan 30, 2021

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.

@Intrepidd
Copy link
Contributor

Intrepidd commented Jan 31, 2021

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

@janko
Copy link

janko commented May 7, 2021

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

@sevab
Copy link

sevab commented Jun 14, 2021

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.

@Kukunin
Copy link

Kukunin commented Jun 17, 2021

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 }) }

@Kukunin
Copy link

Kukunin commented Jun 17, 2021

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.

@Kukunin
Copy link

Kukunin commented Jun 17, 2021

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

@joerdav
Copy link

joerdav commented Jul 1, 2021

+1 this feature would be invaluable

@michelson
Copy link
Contributor

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

@Kukunin
Copy link

Kukunin commented Jul 1, 2021

@michelson you're right, thanks for the reporting. I'll update my gist with the fix soon

@Kukunin
Copy link

Kukunin commented Jul 1, 2021

@michelson found a bug, where the observer pushed a new state every time the back button clicked. Fixed in my gist

@michelson
Copy link
Contributor

@Kukunin thanks for let me know, I can confirm that is working flawlessly :)

@michelson
Copy link
Contributor

@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 ?

@Kukunin
Copy link

Kukunin commented Jul 7, 2021

@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

@michelson
Copy link
Contributor

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)
      }
    }
  }

@m-andreas
Copy link

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

@dhh
Copy link
Member

dhh commented Sep 17, 2021

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.

seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
hotwired#50
hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
hotwired#50
hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[dat-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
hotwired#50
hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[dat-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
hotwired#50
hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[dat-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
Closes hotwired#50
Closes hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[dat-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
Closes hotwired#50
Closes hotwired#361

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's histroy push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[dat-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 17, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Sep 21, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
mhw pushed a commit to mhw/turbo that referenced this issue Oct 9, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
@eohland
Copy link

eohland commented Oct 22, 2021

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

seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Oct 24, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Oct 25, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
@PhilippMeissner
Copy link

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.

seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Nov 9, 2021
Closes hotwired#50
Closes hotwired#361
Closes hotwired#167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits
@dhh dhh closed this as completed in #398 Nov 11, 2021
dhh pushed a commit that referenced this issue Nov 11, 2021
* Add `linkClickIntercepted` to FrameElementDelegate

Expand the `FrameElementDelegate` interface to include a
`linkClickIntercepted` to match its existing
`formSubmissionIntercepted`, then replace a manual `setAttribute` and
`src` assignment with a delegation to the `FrameElementDelegate`
instance.

* Push history state from frame navigations

Closes #50
Closes #361
Closes #167

---

Extend of built-in support for `<a>` elements with [data-turbo-action][]
(with `"replace"` or `"advance"`) to also encompass `<turbo-frame>`
navigations.

Account for the combination of of `[data-turbo-frame]` and
`[data-turbo-action]` to navigate the target `<turbo-frame>` _and_
navigate the page's history push state, supporting:

* `turbo-frame[data-turbo-action="..."]`
* `turbo-frame a[data-turbo-action="..."]`
* `a[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."][data-turbo-action="..."]`
* `form[data-turbo-frame="..."] button[data-turbo-action="..."]`
* `form button[data-turbo-frame="..."][data-turbo-action="..."]`

Whenever a Turbo Frame response is loaded that was initiated from one of
those submitters, forms, anchors, or turbo-frames annotated with a
`[data-turbo-action]`, the subsequent firing `turbo:frame-render` event
will create a `Visit` instance that will skip rendering, won't result in
a network request, and will instead only update the snapshot cache and
history.

[data-turbo-action]: https://turbo.hotwired.dev/handbook/drive#application-visits

* Extract `getAttribute` utility function

For cases where we need to find an attribute value from a collection of
elements, use `getAttribute` instead of a long chain of `||` and `?`
operators.
@xpopov
Copy link

xpopov commented Apr 3, 2023

This issue doesn't mention, but there's easy standalone solution: data-turbo-action="advance"

@Iccr
Copy link

Iccr commented Jun 12, 2024

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

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

Successfully merging a pull request may close this issue.