-
Notifications
You must be signed in to change notification settings - Fork 436
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
Dispatch turbo:frame-load
on turbo-frame
#59
Conversation
So this means we won't be able to listen for Listen for turbo drive events: document.addEventListener('turbo:load', (event) => ())` Listen for frame events: document.querySelector('turbo-frame[id="some_id"]').addEventListener('turbo:load', (event) => console.log('turbo:load', event.target)) |
I have a use-case that uses turbo frames to lazily load some slow and expensive content. I show an activity indicator which needs to hide when the turbo frame is finished loading, so I setup a stimulus controller that uses a import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ['activity', 'content', 'frame']
connect () {
this.observer = new MutationObserver(this.frameMutated.bind(this))
this.observer.observe(this.frameTarget, { attributes: true })
}
disconnect () {
this.observer.disconnect()
delete this.observer
}
showActivity () {
this.activityTarget.hidden = false
this.contentTarget.hidden = true
}
showContent () {
this.activityTarget.hidden = true
this.contentTarget.hidden = false
}
frameMutated () {
if (this.frameTarget.hasAttribute('busy')) {
this.showActivity()
} else {
this.showContent()
}
}
} |
7922187
to
6483ef0
Compare
6483ef0
to
d65f931
Compare
Since `<turbo-frame>` elements are custom elements, the framework has total control over the names of the attributes. There are existing semantics for what we've introduced as `[busy]`: the ARIA guidelines suggest toggling [aria-busy="true"][aria-busy] when an element is loading more content, and `aria-busy="false"` when the content is loaded. This provides an "interface" for loading styles through CSS attribute selectors, and hints to assistive technologies the state of the frame. As an alternative, we could continue to toggle the `[busy]` attribute, and encourage consumer applications to monitor mutations to the `[busy]` attribute (or listen to [`turbo:frame-visit` and `turbo:frame-load` events][events]) to toggle it themselves. [aria-busy]: https://www.w3.org/TR/wai-aria-1.1/#aria-busy [events]: hotwired#59
Since `<turbo-frame>` elements are custom elements, the framework has total control over the names of the attributes. There are existing semantics for what we've introduced as `[busy]`: the ARIA guidelines suggest toggling [aria-busy="true"][aria-busy] when an element is loading more content, and `aria-busy="false"` when the content is loaded. This provides an "interface" for loading styles through CSS attribute selectors, and hints to assistive technologies the state of the frame. As an alternative, we could continue to toggle the `[busy]` attribute, and encourage consumer applications to monitor mutations to the `[busy]` attribute (or listen to [`turbo:frame-visit` and `turbo:frame-load` events][events]) to toggle it themselves. [aria-busy]: https://www.w3.org/TR/wai-aria-1.1/#aria-busy [events]: hotwired#59
Since `<turbo-frame>` elements are custom elements, the framework has total control over the names of the attributes. There are existing semantics for what we've introduced as `[busy]`: the ARIA guidelines suggest toggling [aria-busy="true"][aria-busy] when an element is loading more content, and `aria-busy="false"` when the content is loaded. This provides an "interface" for loading styles through CSS attribute selectors, and hints to assistive technologies the state of the frame. As an alternative, we could continue to toggle the `[busy]` attribute, and encourage consumer applications to monitor mutations to the `[busy]` attribute (or listen to [`turbo:frame-visit` and `turbo:frame-load` events][events]) to toggle it themselves. [aria-busy]: https://www.w3.org/TR/wai-aria-1.1/#aria-busy [events]: hotwired#59
Since `<turbo-frame>` elements are custom elements, the framework has total control over the names of the attributes. There are existing semantics for what we've introduced as `[busy]`: the ARIA guidelines suggest toggling [aria-busy="true"][aria-busy] when an element is loading more content, and `aria-busy="false"` when the content is loaded. This provides an "interface" for loading styles through CSS attribute selectors, and hints to assistive technologies the state of the frame. As an alternative, we could continue to toggle the `[busy]` attribute, and encourage consumer applications to monitor mutations to the `[busy]` attribute (or listen to [`turbo:frame-visit` and `turbo:frame-load` events][events]) to toggle it themselves. [aria-busy]: https://www.w3.org/TR/wai-aria-1.1/#aria-busy [events]: hotwired#59
Paired with hotwired/turbo#59 Document the new `<turbo-frame>` events.
I've opened hotwired/turbo-site#41 to document these new events. |
1a8a719
to
35c7803
Compare
Paired with hotwired/turbo#59 Document the new `<turbo-frame>` events.
35c7803
to
a8cc4e7
Compare
@seanpdoyle Not sure if that's the expected behaviour but there's something odd with document.addEventListener("turbo:before-render", (event) => {
// event.detail.newBody is available here
// but cant preventDefault
//
// how to implement a custom assignNewBody?
// or applying morphdom here?
event.preventDefault()
}) |
@rainerborene the only I believe that's by design. The |
@seanpdoyle Thanks for the reference. I know this decision is left to @sstephenson and @javan. But shouldn't that be reconsidered? Shouldn't we give the tools so developers can decide for themselves? That was a common use case with const nativeReplaceChild = document.documentElement.replaceChild
document.documentElement.replaceChild = (newChild, oldChild) => {
const oldContainer = oldChild.querySelector("#app")
const newContainer = newChild.querySelector("#app")
if (oldContainer && newContainer) {
document.body.replaceChild(newContainer, oldContainer)
} else {
nativeReplaceChild.call(document.documentElement, newChild, oldChild)
}
} |
@rainerborene maybe I'm misunderstanding your use case, but would you be able to operate on the What is it that you're trying to accomplish by cancelling the event, and in what way is the event's constraints preventing you from doing that? |
@seanpdoyle Say you have a few plugins installed that injects custom elements inside the |
Would you be able to achieve the desired outcome by modifying the new body element with those plugins and letting Turbo continue with its rendering process? |
That would be a possibility except that our users can install any plugin they want via custom scripts inserted before |
The Is there anything that's blocking this PR? |
Paired with hotwired/turbo#59 Document the new `<turbo-frame>` events.
FYI, ran into an issues today this PR would solve. Thanks for build a solution. |
Closes #54 Closes hotwired/turbo-rails#56 Dispatch `turbo:frame-load` lifecycle event when `<turbo-frame>` element is navigated and finishes loading. The events bubble up, with the `<turbo-frame>` element as the target. Originally, this pull request involved numerous events, but in the spirit of experimentation, we'll start with the one and see if others are necessary.
ad05002
to
a275260
Compare
turbo:frame-load
on turbo-frame
Paired with hotwired/turbo#59 Document the `<turbo-frame>` elements new `turbo:frame-load` event.
@seanpdoyle I've noticed that over time the scope of this PR has shrunk significantly. While in general I believe that's a good idea in most cases, limiting this PR to only a single event greatly reduces it's usefulness. The most common use case for this feature would, IMO, be to toggle various forms of loading states, to indicate a frame's current status. This is not possible when only a single event fires after the frame load. I think at least one additional event needs to make a comeback, something like a |
@panda-madness you're correct, I've reduced the scope of this proposed change in the interest of limiting the maintenance burden and commitment to supporting new events.
My intention is that between the existing In my opinion, writing a MutationObserver to monitor when a request toggles the |
I don't fully understand your position, since between I want to point out that I'm not advocating for the restoration of all events that were present in the initial version of this PR, just the smallest subset that could have meaningful utility (really only |
This is exactly something I am looking for for proper initialization of a datatable inside my turbo frame. Would love to see it merged. |
I needed this feature on a recent project where the target of a form connected to a turbo-frame. When a user submits the form, I disable the submit button and show a loading animation. When the turbo-frame completes loading, I re-enable the submit button. My first attempt was:
These were the only turbo events I could use to make this work. However, the one problem I ran into is if a user clicks submit a second time with the same parameters. I ended up writing a stimulus controller to fire events // turbo-frame_controller.js
import ApplicationController from './application_controller';
const ATTRIBUTE_NAMES = ['src', 'busy'];
const frameSrcUpdated = (event) =>
event.type === 'attributes' && ATTRIBUTE_NAMES.indexOf(event.attributeName) > -1;
export default class extends ApplicationController {
initialize() {
this.observeMutations(this.dispatchEvents, this.element, { attributes: true });
}
dispatchEvents(events) {
if (events.some(frameSrcUpdated)) {
this.dispatchEvent();
}
}
dispatchEvent() {
let eventName = 'before-render';
if (this.element.hasAttribute('busy')) {
eventName = 'fetch-start';
}
this.dispatch(eventName);
this.dispatch(`${eventName}#${this.element.id}`);
}
} This provides 4 events:
|
Hello, |
can be useful to someone with same probelm # app/javascript/controllers/contacts_controller.js
import {Controller} from "stimulus"
export default class extends Controller {
static targets = ["loading", "contacts"]
this.renderSomething(mutationsList, observer) {
console.log("subtree changed!");
}
connect() {
console.log("contacted!");
this.observer = new MutationObserver(this.renderSomething);
this.observer.observe(this.element, {childList: true});
}
disconnect() {
console.log("dis-contacted!");
this.observer.disconnect();
}
}
# in slim view
= turbo_frame_tag :feed, src: contacts_index_path, loading: :eager, data: { controller: 'contacts' } do
= image_tag 'loading.gif', data: { 'contacts-target': 'loading' } it waits to |
This looks good @Pepan. |
Is this still in the pipeline? Looks like a lot of progress was made, but it was never merged? |
Most likely someone else is going to need to need to reproduce this feature in another PR and pray someone from what's little left of the "core team" at Basecamp notices, agrees with, and merges it. At this point, it's becoming very unlikely that this is going to become a feature. |
I really wanted this to exist! In lieu of this I've used a MutationObserver to replicate it. It works a treat for what I wanted to do.
and then where you need to listen for the event..
I've avoided using the same namespace as turbo just in case something happens in the future. Also depending on your use case there might be better observer options. |
Closes #54
Closes #118
Closes hotwired/turbo-rails#56
Dispatch
turbo:frame-load
lifecycle event when<turbo-frame>
elementis navigated and finishes loading. The events bubble up, with the
<turbo-frame>
element as the target.Originally, this pull request involved numerous events, but in the
spirit of experimentation, we'll start with the one and see if others
are necessary.