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

Lazy-loaded frames are treated as loaded #214

Open
ghost opened this issue Mar 21, 2021 · 5 comments
Open

Lazy-loaded frames are treated as loaded #214

ghost opened this issue Mar 21, 2021 · 5 comments
Labels
bug Something isn't working

Comments

@ghost
Copy link

ghost commented Mar 21, 2021

Suppose you have a menu, loaded in with a frame (src="/menu"), that is initially off screen (with a <details>/<summary> tag pairing, say). By setting loading="lazy" on the frame, we can defer loading until someone opens the menu. Great. But what if you want to focus the first menu item when the menu loads?

We need to wait for the frame to load, because it will load in all our menu items. We have nothing to focus until the frame loads. As frame elements have a loaded promise, the solution seems really easy — a Stimulus controller action something like:

async adjustFocus() {
  await this.frameElement.loaded
  this.focusFirstItem()
}

This won’t work, though. Frame elements are instantiated with the promise resolved. And the only time the promise gets changed to a pending state is when we load the frame src. But because we’re lazy loading, we don’t load the frame src until we open the menu, so when the adjustFocus method above runs, the promise is already resolved. It doesn’t need to await the promise, it just goes on to focusFirstItem. Yet the frame hasn’t loaded, we have no items, so this is going to fail.

You can make this work, with workarounds (I noticed HEY has a hidden <a> tag, pointing to a frame, that gets programatically clicked when you open a menu). But this feels like something that should just work.

@ghost
Copy link
Author

ghost commented Mar 21, 2021

I think the solution here is something along the lines of: (1) assign a thenable object to this.element.loaded in FrameController#connect and (2) resolve that "thenable", as a promise, in FrameController#elementAppearedInViewport. But I’m too much of a typescript noob to make it work.

This is a first draft of the implementation I had in mind:

//  src/core/frames/frame_controller.ts

export class FrameController {
  // ...

  connect() {
    if (this.loadingStyle == FrameLoadingStyle.lazy) {
      this.element.loaded = { then: (resolve) => resolve() }
      this.appearanceObserver.start()
    }
    
    // ...
  }
  
  // ...
  
  async elementAppearedInViewport(element: Element) {
    const promise = this.element.loaded
    await this.loadSourceURL()
    await Promise.resolve(promise)
  }
}

@sstephenson sstephenson added the bug Something isn't working label Apr 10, 2021
@sstephenson
Copy link
Contributor

Agree, you should be able to wait on the loaded promise for a lazy frame.

@mattalat
Copy link

mattalat commented Dec 2, 2022

I believe this is still an issue. There's also the question of why lazy frames have fulfilled .loaded promises and are .complete == true before they're visible on the screen.

@bellef
Copy link

bellef commented Feb 19, 2024

Upvoting as it's still an issue 👍

@tvongaza
Copy link

tvongaza commented May 29, 2024

Also up voting, just ran into this bug.

Here is my work around:

async adjustFocus() {
  await new Promise<void>(function (resolve) {
    this.frameElement.addEventListener("turbo:frame-load", function frameLoadedListener() {
      this.frameElement.removeEventListener("turbo:frame-load", frameLoadedListener);
      resolve();
    });
  });
  this.focusFirstItem()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Development

No branches or pull requests

4 participants