Hi everyone,
I was trying to figure out how to show a spinner inside my turbo-frame on every turbo request.
The first time the page loads, the spinner shows, and the turbo-frame content gets replaced
asynchronously with the content loaded from “src”.
So far so good.
The problem is when I initiate a subsequent turbo navigation within the frame, the loader that was rendered on the initial page load doesn’t show anymore.
From the user’s perspective, the form submits and 10 seconds later the turbo-frame’s content gets switched out with the new HTML (this is a very slow endpoint).
So I wanted to re-display the loader that I had on the initial page load on every turbo request.
This is how I achieved it, but I would like to know if there is a better way?
<!-- This is in the initial document -->
<turbo-frame id="my_frame" src="/my/endpoint" data-controller="turbo-frame-async-fix">
<div class='my-loader'></div>
</turbo-frame>
When a turbo navigation occurs inside the frame, this stimulus controller will detect
it by observing the busy
attribute of the turbo-frame, and replace it’s children with
the initial content
// controllers/turbo_frame_async_fix_controller.js
import { Controller } from '@hotwired/stimulus'
import { useMutation } from 'stimulus-use'
export default class extends Controller {
connect() {
// Copy the initial content of the turbo-frame, so we can put it back in when frame becomes busy.
this.loaderHTML = [...this.element.childNodes].map((node) => node.cloneNode(true))
useMutation(this, { attributes: true })
}
mutate(entries) {
entries.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'busy') {
if (mutation.target.attributes.busy) {
this.element.replaceChildren(...this.loaderHTML)
}
}
})
}
}