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

Dispatch visit events on the initiator link or form #695

Merged
merged 6 commits into from
Sep 13, 2022

Conversation

domchristie
Copy link
Contributor

In a similar vein to #367, this pull request dispatches turbo:visit, turbo:before-visit, turbo:before-fetch-request, and turbo:before-fetch-response on the initiator link or form (where applicable), rather than solely on the html element.

Why? When developing libraries that build on top of Turbo, it'd be nice to customise behaviour based on HTML attributes of the clicked link or submitted form. For example, if I were building an animation library and wished to disable a transition when clicking a particular element, I could do:

<a href="" data-animate="false">Do not animate me</a>

I could then use event.target.dataset.animate in the turbo:visit event to conditionally animate.
Other examples are mentioned in #99.

Closes #99

@@ -50,7 +50,8 @@ export type VisitOptions = {
restorationIdentifier?: string
shouldCacheSnapshot: boolean
frame?: string
acceptsStreamResponse: boolean
acceptsStreamResponse: boolean,
initiator?: HTMLAnchorElement | HTMLFormElement
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we declare this property without the ? optional modifier? VisitOption instances are typically passed around as Partial<VisitOption> types, so the Partial makes it optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without ? TypeScript moans because it's not present in defaultOptions:

Property 'initiator' is missing in type '{ action: "advance"; historyChanged: false; visitCachedSnapshot: () => void; willRender: true; updateHistory: true; shouldCacheSnapshot: true; acceptsStreamResponse: false; }' but required in type 'VisitOptions'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could its default value be document.documentElement, since that's what it's currently dispatched from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not strongly opposed to this, but just wonder if having an undefined initiator usefully signals that the Visit is a programmatic one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not strongly opposed to this, but just wonder if having an undefined initiator usefully signals that the Visit is a programmatic one?

I suppose it doesn't really matter since a programmatic visit could be determined if visit.initiator === document.documentElement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if we want a default, perhaps initiator is the wrong name for this? i.e. for programmatic visits the HTML element doesn't "initiate" the visit, the event just gets dispatched on it. It's not as clear, but perhaps Visit#element would work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default initiator implemented in: 36dd97b

@@ -191,17 +191,17 @@ export class Session
)
}

followedLinkToLocation(link: Element, location: URL) {
followedLinkToLocation(link: HTMLAnchorElement, location: URL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I appreciate the type narrowing, so long as the initiator can invoke dispatchEvent, we might be able to achieve the outcome without disrupting these interfaces.

I've been tempted to narrow this delegate method before, but I have a hunch that there is some Custom Element support reason that it's still Element.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

95590c4 uses Element for all references

@dhh dhh merged commit 8871817 into hotwired:main Sep 13, 2022
dhh pushed a commit that referenced this pull request Sep 19, 2022
…723)

The inclusion of the `initiator` in `VisitOptions` causes a JS crash in
the Turbo iOS adapter.

This is because Turbo iOS's implementation of `visitProposedToLocation`
relies on being able to pass the entire options struct to native code.
Initiator is an `Element`, which can't be passed in this way, resulting
in a `DataCloneError` exception.

There is some discussion on this [GitHub issue][]. We're reverting the
change for the moment, until we have an implementation of it that works
with all adapters.

[GitHub issue]: #720

This reverts commit 8871817.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Events: Accessing the link or form element in dispatched events
3 participants