-
Notifications
You must be signed in to change notification settings - Fork 435
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
Some thoughts about turbo-frame-tags when the redirected render doesn't contain any turbo-frame-tags that match the original #94
Comments
Thanks for opening this issue! What we intend to do when there's no matching frame in the response is to empty out the frame's contents and log a message to the console. Hoping to get that in for the next beta. |
Thanks for the quick response @sstephenson! I wonder if there could be an option which would default to the functionality as you're describing, but a configuration override which would render the response and update the history. It seems like this would give best of both worlds. |
It's an interesting idea, but I think it's more important to preserve the contract that navigation in a frame stays in the frame unless you explicitly opt out with Generally we avoid configuration options unless we have no other choice. But I'd be open to adding any special hooks necessary to allow you to implement this outside of Turbo. |
That's understandable. And I think having some hooks would be sufficient if we also got the responseHTML along with some indication of the element being found or not (the DOM was updated with a new turbo-frame-tag). We could then probably use the |
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `url:` key, and the `response:` key containing: * `redirected: boolean` * `responseHTML: string` * `statusCode: number` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", ({ target, detail: { url, response } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { const visitOptions = response Turbo.visit(url, visitOptions) } }) [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `url:` key, and the `response:` key containing: * `redirected: boolean` * `responseHTML: string` * `statusCode: number` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", ({ target, detail: { url, response } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { const visitOptions = response Turbo.visit(url, visitOptions) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `url:` key, and the `response:` key containing: * `redirected: boolean` * `responseHTML: string` * `statusCode: number` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", ({ target, detail: { url, response } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { const visitOptions = response Turbo.visit(url, visitOptions) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `url:` key, and the `response:` key containing: * `redirected: boolean` * `responseHTML: string` * `statusCode: number` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", ({ target, detail: { url, response } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { Turbo.visit(url, { response }) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. To transform the `FetchResponse` into a `Visit`, clients can extract: * `redirected: boolean` * `statusCode: number` * `responseHTML: Promise<string>` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", async ({ target, detail: { fetchResponse } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { const { location, redirected, statusCode, responseHTML } = fetchResponse const response = { redirected, statusCode, responseHTML: await responseHTML } Turbo.visit(location, { response }) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. To transform the `FetchResponse` into a `Visit`, clients can extract: * `redirected: boolean` * `statusCode: number` * `responseHTML: Promise<string>` Event listeners for `turbo:frame-missing` can forward the `detail` directly to a `Turbo.visit` call: ```js addEventListener("turbo:frame-missing", async ({ target, detail: { fetchResponse } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { const { location, redirected, statusCode, responseHTML } = fetchResponse const response = { redirected, statusCode, responseHTML: await responseHTML } Turbo.visit(location, { response }) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", async ({ target, detail: { fetchResponse, visit } }) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(target)) { visit({ action: "replace" }) } }) ``` [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", (event) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(event.target)) { const { detail: { fetchResponse, visit } } = event event.preventDefault() visit() } }) ``` The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. Similarly, if the reason for the missing frame is particular to the page referenced by the element's `[src]` attribute, this is an opportunity to change that attribute (calling `event.target.removeAttribute("src")`, for example) before navigating away so that re-visiting the page by navigating backward in the Browser's history doesn't automatically load the frame and re-trigger another `turbo:frame-missing` event. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31 cancelable event
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", (event) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(event.target)) { const { detail: { fetchResponse, visit } } = event event.preventDefault() visit() } }) ``` The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. Similarly, if the reason for the missing frame is particular to the page referenced by the element's `[src]` attribute, this is an opportunity to change that attribute (calling `event.target.removeAttribute("src")`, for example) before navigating away so that re-visiting the page by navigating backward in the Browser's history doesn't automatically load the frame and re-trigger another `turbo:frame-missing` event. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", (event) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(event.target)) { const { detail: { fetchResponse, visit } } = event event.preventDefault() visit() } }) ``` The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. Similarly, if the reason for the missing frame is particular to the page referenced by the element's `[src]` attribute, this is an opportunity to change that attribute (calling `event.target.removeAttribute("src")`, for example) before navigating away so that re-visiting the page by navigating backward in the Browser's history doesn't automatically load the frame and re-trigger another `turbo:frame-missing` event. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", (event) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(event.target)) { const { detail: { fetchResponse, visit } } = event event.preventDefault() visit() } }) ``` The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. Similarly, if the reason for the missing frame is particular to the page referenced by the element's `[src]` attribute, this is an opportunity to change that attribute (calling `event.target.removeAttribute("src")`, for example) before navigating away so that re-visiting the page by navigating backward in the Browser's history doesn't automatically load the frame and re-trigger another `turbo:frame-missing` event. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. The `event.detail.visit` key provides handlers with a way to transform the `fetchResponse` into a `Turbo.visit()` call without any knowledge of the internal structure or logic necessary to do so. Event listeners for `turbo:frame-missing` can invoke the `event.detail.visit` directly to invoke `Turbo.visit()` behind the scenes. The yielded `visit()` function accepts `Partial<VisitOptions>` as an optional argument: ```js addEventListener("turbo:frame-missing", (event) => { // the details of `shouldRedirectOnMissingFrame(element: FrameElement)` // are up to the application to decide if (shouldRedirectOnMissingFrame(event.target)) { const { detail: { fetchResponse, visit } } = event event.preventDefault() visit() } }) ``` The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. Similarly, if the reason for the missing frame is particular to the page referenced by the element's `[src]` attribute, this is an opportunity to change that attribute (calling `event.target.removeAttribute("src")`, for example) before navigating away so that re-visiting the page by navigating backward in the Browser's history doesn't automatically load the frame and re-trigger another `turbo:frame-missing` event. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. Unless it's canceled (by calling `event.preventDefault()`), Turbo Drive will visit the frame's URL as a full-page navigation. The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
Closes [hotwired#432][] Follow-up to [hotwired#94][] Follow-up to [hotwired#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. Unless it's canceled (by calling `event.preventDefault()`), Turbo Drive will visit the frame's URL as a full-page navigation. The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. [contract]: hotwired#94 (comment) [hotwired#432]: hotwired#432 [hotwired#94]: hotwired#94 [hotwired#31]: hotwired#31
* Introduce `turbo:frame-missing` event Closes [#432][] Follow-up to [#94][] Follow-up to [#31][] When a response from _within_ a frame is missing a matching frame, fire the `turbo:frame-missing` event. There is an existing [contract][] that dictates a request from within a frame stays within a frame. However, if an application is interested in reacting to a response without a frame, dispatch a `turbo:frame-missing` event. The event's `target` is the `FrameElement`, and the `detail` contains the `fetchResponse:` key. Unless it's canceled (by calling `event.preventDefault()`), Turbo Drive will visit the frame's URL as a full-page navigation. The event listener is also a good opportunity to change the `<turbo-frame>` element itself to prevent future missing responses. For example, if the reason the frame is missing is access (an expired session, for example), the call to `visit()` can be made with `{ action: "replace" }` to remove the current page from Turbo's page history. [contract]: #94 (comment) [#432]: #432 [#94]: #94 [#31]: #31 * re-run CI * issue a new request for the full page of content * Add console warning if a full-page visit is triggered as a result of missing matching frame Co-authored-by: David Heinemeier Hansson <david@hey.com>
Firstly and perhaps most importantly, thank you so much for Turbo. I really enjoy it!
Let's take a very simple example of a new Rails application with
hotwire-rails
created. We create a scaffold for products and within the form partial of the products, we wrap the form elements in aturbo_frame_tag 'product'
. The expected behavior now is if there are any validation errors, it renders the page with the validation errors. Turbo accomplishes this already.However, if there are no validation errors and the record persists successfully, then I would expect it to render the display of the show page. However, because there is no turbo-frame-tag on the show page, it doesn't do anything and we still have the form displayed.
I think that this is very unexpected behavior even though things make sense with turbo. I know that this functionality has caused me a few headaches initially and also the community with trying to find proper workarounds.
I'm not the best Javascript person out there, but dabbled around in it a bit, so I would leave any PR to the experts. However, I did pull in the turbo library from turbo-rails into my application to tinker with things and came up with a minor-ish fix.
Within the
FrameController
class, I modified theloadResponse
function. I added theconst html
which before was simply inside of thefragmentFromHTML
, but the important bit is theelse
statement. It looks like theelement
is the rendered result from our Rails application and the turbo-frame-tag has been plucked out as a fragment of the original rendered response. So, if noelement
is found, then that means that the rendered page did not have any turbo-frame-tags which means that our UI will look like it did not perform any action.My thought here is to replace the body of the current page, with the entire contents of the rendered response in the event that no turbo-frame-tag was found within the response.
The text was updated successfully, but these errors were encountered: