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

Eager/lazy loaded turbo-frames are reloaded after snapshot restoration #429

Closed
tobyzerner opened this issue Oct 25, 2021 · 6 comments · Fixed by #487
Closed

Eager/lazy loaded turbo-frames are reloaded after snapshot restoration #429

tobyzerner opened this issue Oct 25, 2021 · 6 comments · Fixed by #487

Comments

@tobyzerner
Copy link

tobyzerner commented Oct 25, 2021

Navigating away from a page, then going back, causes all eager/lazy loaded turbo frames to be reloaded on the original page after the snapshot is restored.

Simple example:

<turbo-frame id="page_1" target="_top">
  <a href="/posts/1">Post 1</a>
  <turbo-frame id="page_2" src="?page=2" loading="lazy" target="_top"></turbo-frame>
</turbo-frame>

When you visit the page initially, the #page_2 frame lazy loads as expected. If you click on "Post 1" and then go back, after the page snapshot is restored, the #page_2 frame reloads, which is undesirable.

@tobyzerner tobyzerner changed the title Snapshot restoration causes eager/lazy loaded turbo-frames to be reloaded Eager/lazy loaded turbo-frames are reloaded after snapshot restoration Oct 25, 2021
@tleish
Copy link
Contributor

tleish commented Nov 12, 2021

One way to do this would be to remove src="?page=2" from the turbo-frame after it renders. There might be some cases where you wouldn't want to do this by default.

As with many other solutions, you could easily control this with a simple stimulus controller. Something like:

class AttributeController extends Controller {
  remove({ params: {name}}) {
    this.element.removeAttribute(name);
  }
}
<turbo-frame id="page-2020-12" src="page2.html" data-controller="attribute" data-action="turbo:frame-load->attribute#remove" data-attribute-name-param="src">
  <a href="page2.html">Page 2s</a>
</turbo-frame>

Or, if you wanted to do it for all your turbo-frames, you could just add a global event listener:

document.addEventListener('turbo:frame-load', function({ srcElement }) {
  srcElement.removeAttribute('src');
});

@tobyzerner
Copy link
Author

@tleish Thanks, that's a great workaround!

@seanpdoyle
Copy link
Contributor

seanpdoyle commented Nov 25, 2021

@tobyzerner I recognize that the example you shared is pseudo-code, but in the real scenario, is <turbo-frame id="page_2"> visible in the viewport when you navigate back? This feels like an infinite-scroll use case, and I assume that the page_2 frame is loaded because it's scrolled into view. When you restore the Snapshot when navigating back, is the page still scrolled to the same depth?

The code @tleish has the right idea: intervene when the frame has loaded (although I'd recommend accessing the <turbo-frame> through Event.target, since Event.srcElement is deprecated).

Removing the [src] attribute is an effective way to prevent a duplicate navigation. There is also the [disabled] attribute if preserving the [src] is important, and you'd like to re-load the Frame at a later point in time.

@tobyzerner
Copy link
Author

@seanpdoyle Yes that's correct – it's an infinite scroll use case. When you navigate back, the page scroll is restored and so the page_2 frame is immediately in view, and Turbo reloads it.

The [disabled] attribute is a nice workaround too. However, I just note I still think of these as "workarounds" – intuitively I would expect the frame to not be reloaded on restoration, without having to intervene, hence why I didn't close this one just yet. Is there an easy/sensible path to preserving the frame's state across restoration visits by default (perhaps by adding something like a [loaded] attribute when the frame has loaded), or is it too much complexity for too little benefit?

@seanpdoyle
Copy link
Contributor

@tobyzerner I like [loaded] as an attribute name, but it collides with the FrameElement.loaded property which exposes a reference to the currently loading Promise instance.

Before bike-shedding a name, I'd like to make sure that exposing an attribute is worthwhile. While attempting to resolve another issue, I've implemented a [loaded] attribute on a branch on my fork:

https://github.com/hotwired/turbo/compare/seanpdoyle:frame-loaded-attribute

I think it might resolve some of the issues you're experiencing. Could you try it out?

seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"loaded"` value. When making internal modifications to
those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"loaded"` value. When making internal modifications to
those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"loaded"` value. When making internal modifications to
those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"loaded"` value. When making internal modifications to
those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameElement` that's returned from the
`FrameElement.observedAttributes` static property, including the `"src"`
or `"loaded"` value. When making internal modifications to those values,
it's important to temporarily disable observation callbacks to avoid
unnecessary requests and to limit the potential for infinitely recursing
loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameElement` that's returned from the
`FrameElement.observedAttributes` static property, including the `"src"`
or `"loaded"` value. When making internal modifications to those values,
it's important to temporarily disable observation callbacks to avoid
unnecessary requests and to limit the potential for infinitely recursing
loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Dec 1, 2021
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameElement` that's returned from the
`FrameElement.observedAttributes` static property, including the `"src"`
or `"loaded"` value. When making internal modifications to those values,
it's important to temporarily disable observation callbacks to avoid
unnecessary requests and to limit the potential for infinitely recursing
loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
@kidlj
Copy link

kidlj commented Jun 18, 2022

I'm having the same issue while making an infinite scroll page, just like what @tobyzerner did. So why does turbo-frame with src have to reload after snapshot restoration? Or if it needs to, can we add an attribute to turbo-frame to control this behavior?

I saw the ongoing PR, Since loaded is used by another property, how about we add an error attribute, when auto loading succeeds it's not set, but when things go wrong(400,401,404,500 etc.) it's set to the status code? Thanks.

seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Jun 19, 2022
Closes hotwired#429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameElement` that's returned from the
`FrameElement.observedAttributes` static property, including the `"src"`
or `"loaded"` value. When making internal modifications to those values,
it's important to temporarily disable observation callbacks to avoid
unnecessary requests and to limit the potential for infinitely recursing
loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
seanpdoyle added a commit to seanpdoyle/turbo that referenced this issue Jun 19, 2022
Closes hotwired#429

---

Introduce the `<turbo-frame complete>` boolean attribute. The
attribute's absence indicates that the frame has not yet been loaded,
and is ready to be navigated. Its presence means that the contents of
the frame have been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[complete]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[complete]` attribute if the element [isConnected][] to the
document, so that the `[complete]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[complete]`
attribute, which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("complete")`.

A side-effect of introducing the `[complete]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"complete"` value. When making internal modifications
to those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
@dhh dhh closed this as completed in #487 Jun 19, 2022
dhh pushed a commit that referenced this issue Jun 19, 2022
* Expose Frame load state via `[loaded]` attribute

Closes #429

---

Introduce the `<turbo-frame loaded>` boolean attribute. The attribute's
absence indicates that the frame has not yet been loaded, and is ready
to be navigated. Its presence means that the contents of the frame have
been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[loaded]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[loaded]` attribute if the element [isConnected][] to the
document, so that the `[loaded]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[loaded]` attribute,
which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("loaded")`.

A side-effect of introducing the `[loaded]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameElement` that's returned from the
`FrameElement.observedAttributes` static property, including the `"src"`
or `"loaded"` value. When making internal modifications to those values,
it's important to temporarily disable observation callbacks to avoid
unnecessary requests and to limit the potential for infinitely recursing
loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected

* Expose Frame load state via `[complete]` attribute

Closes #429

---

Introduce the `<turbo-frame complete>` boolean attribute. The
attribute's absence indicates that the frame has not yet been loaded,
and is ready to be navigated. Its presence means that the contents of
the frame have been fetch from its `[src]` attribute.

Encoding the load state into the element's HTML aims to integrate with
Snapshot caching. Once a frame is loaded, navigating away and then
restoring a page's state from an Historical Snapshot should preserve the
fact that the contents are already loaded.

For both `eager` and `lazy` loaded frames, changing the element's
`[src]` attribute (directly via JavaScript, or by clicking an `<a>`
element or submitting a `<form>` element) will remove the `[complete]`
attribute. Eager-loaded frames will immediately initiate a request to
fetch the contents, and Lazy-loaded frames will initiate the request
once they enter the viewport, or are changed to be eager-loading.

When the `[src]` attribute is changed, the `FrameController` will only
remove the `[complete]` attribute if the element [isConnected][] to the
document, so that the `[complete]` attribute is not modified prior to
Snapshot Caching or when re-mounting a Cached Snapshot.

The act of "reloading" involves the removal of the `[complete]`
attribute, which can be done either by `FrameElement.reload()` or
`document.getElementById("frame-element").removeAttribute("complete")`.

A side-effect of introducing the `[complete]` attribute is that the
`FrameController` no longer needs to internally track:

1. how the internal `currentURL` value compares to the external
  `sourceURL` value
2. whether or not the frame is "reloadable"

By no longer tracking the `sourceURL` and `currentURL` separately, the
implementation for the private `loadSourceURL` method can be simplified.
Since there is no longer a `currentURL` property to rollback, the `try {
... } catch (error) { ... }` can be omitted, and the `this.sourceURL`
presence check can be incorporated into the rest of the guard
conditional.

Finally, this commit introduce the `isIgnoringChangesTo()` and
`ignoringChangesToAttribute()` private methods to disable
FrameController observations for a given period of time. For example,
when setting the `<turbo-frame src="...">` attribute, previous
implementation would set, then check the value of a
`this.settingSourceURL` property to decide whether or not to fire
attribute change callback code. This commit refines that pattern to
support any property of the `FrameController`, including the
`"sourceURL"` or `"complete"` value. When making internal modifications
to those values, it's important to temporarily disable observation
callbacks to avoid unnecessary requests and to limit the potential for
infinitely recursing loops.

[isConnected]: https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected
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.

4 participants