Code Monkey home page Code Monkey logo

Comments (34)

bfitch avatar bfitch commented on July 21, 2024 21

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

Intrepidd avatar Intrepidd commented on July 21, 2024 15

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

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

from turbo.

xpopov avatar xpopov commented on July 21, 2024 9

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

from turbo.

Intrepidd avatar Intrepidd commented on July 21, 2024 8

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.

PhilippMeissner avatar PhilippMeissner commented on July 21, 2024 8

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.

nickjj avatar nickjj commented on July 21, 2024 7

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.

weaverryan avatar weaverryan commented on July 21, 2024 5

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.

eohland avatar eohland commented on July 21, 2024 4

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.

michiels avatar michiels commented on July 21, 2024 2

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.

sevab avatar sevab commented on July 21, 2024 2

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.

m-andreas avatar m-andreas commented on July 21, 2024 2

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

seanpdoyle avatar seanpdoyle commented on July 21, 2024 1

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.

Kukunin avatar Kukunin commented on July 21, 2024 1

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

from turbo.

seanpdoyle avatar seanpdoyle commented on July 21, 2024

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.

nickjj avatar nickjj commented on July 21, 2024

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.

from turbo.

timmhirsens avatar timmhirsens commented on July 21, 2024

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.

Intrepidd avatar Intrepidd commented on July 21, 2024

from turbo.

Intrepidd avatar Intrepidd commented on July 21, 2024

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.

tgalopin avatar tgalopin commented on July 21, 2024

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.

wlmeurer avatar wlmeurer commented on July 21, 2024

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.

nickjj avatar nickjj commented on July 21, 2024

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.

janko avatar janko commented on July 21, 2024

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

Kukunin avatar Kukunin commented on July 21, 2024

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.

Kukunin avatar Kukunin commented on July 21, 2024

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.

Kukunin avatar Kukunin commented on July 21, 2024

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.

joerdav avatar joerdav commented on July 21, 2024

+1 this feature would be invaluable

from turbo.

michelson avatar michelson commented on July 21, 2024

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

Kukunin avatar Kukunin commented on July 21, 2024

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

from turbo.

michelson avatar michelson commented on July 21, 2024

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

from turbo.

michelson avatar michelson commented on July 21, 2024

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

Kukunin avatar Kukunin commented on July 21, 2024

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

michelson avatar michelson commented on July 21, 2024

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.

dhh avatar dhh commented on July 21, 2024

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.

Iccr avatar Iccr commented on July 21, 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.

from turbo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.