From c261589f31af589a674b0561b5844d27a32ce188 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 30 Sep 2021 15:02:13 -0700 Subject: [PATCH] Replace LinkInterceptor with LinkClickObserver Follow-up to https://github.com/hotwired/turbo/pull/382 --- In the same style as [hotwired/turbo#382][], replace instances of `LinkInterceptor` with `LinkClickObserver`, making the necessary interface changes in classes that used to extend `LinkInterceptorDelegate`. Conditional logic that was once covered in the `LinkInterceptor` is moved into the predicate methods of the delegates (for example, the `FrameController.willFollowLinkToLocation` and `FrameRedirector.willFollowLinkToLocation`). Since the recently introduced [FormLinkInterceptor][] was named after the `LinkInterceptor`, this commit also renames that class (and its call sites) to match the `LinkClickObserver`-suffix, along with the `LinkInterceptorDelegate` method structure. [hotwired/turbo#382]: https://github.com/hotwired/turbo/pull/382 [FormLinkInterceptor]: https://github.com/hotwired/turbo/commit/f8a94c5f761c4f080d68a87459d3916eb4fb6993 --- src/core/frames/frame_controller.ts | 22 ++++----- src/core/frames/frame_redirector.ts | 31 +++++++----- src/core/frames/link_interceptor.ts | 57 ----------------------- src/core/session.ts | 8 ++-- src/elements/frame_element.ts | 4 +- src/observers/form_link_click_observer.ts | 8 ++-- 6 files changed, 40 insertions(+), 90 deletions(-) delete mode 100644 src/core/frames/link_interceptor.ts diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 85254930d..bcaaa57c1 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -23,7 +23,7 @@ import { ViewDelegate, ViewRenderOptions } from "../view" import { getAction, expandURL, urlsAreEqual, locationIsVisitable } from "../url" import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer" import { FrameView } from "./frame_view" -import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" +import { LinkClickObserver, LinkClickObserverDelegate } from "../../observers/link_click_observer" import { FormLinkClickObserver, FormLinkClickObserverDelegate } from "../../observers/form_link_click_observer" import { FrameRenderer } from "./frame_renderer" import { session } from "../index" @@ -39,14 +39,14 @@ export class FrameController FormSubmissionDelegate, FrameElementDelegate, FormLinkClickObserverDelegate, - LinkInterceptorDelegate, + LinkClickObserverDelegate, ViewDelegate> { readonly element: FrameElement readonly view: FrameView readonly appearanceObserver: AppearanceObserver readonly formLinkClickObserver: FormLinkClickObserver - readonly linkInterceptor: LinkInterceptor + readonly linkClickObserver: LinkClickObserver readonly formSubmitObserver: FormSubmitObserver formSubmission?: FormSubmission fetchResponseLoaded = (_fetchResponse: FetchResponse) => {} @@ -65,7 +65,7 @@ export class FrameController this.view = new FrameView(this, this.element) this.appearanceObserver = new AppearanceObserver(this, this.element) this.formLinkClickObserver = new FormLinkClickObserver(this, this.element) - this.linkInterceptor = new LinkInterceptor(this, this.element) + this.linkClickObserver = new LinkClickObserver(this, this.element) this.restorationIdentifier = uuid() this.formSubmitObserver = new FormSubmitObserver(this, this.element) } @@ -79,7 +79,7 @@ export class FrameController this.loadSourceURL() } this.formLinkClickObserver.start() - this.linkInterceptor.start() + this.linkClickObserver.start() this.formSubmitObserver.start() } } @@ -89,7 +89,7 @@ export class FrameController this.connected = false this.appearanceObserver.stop() this.formLinkClickObserver.stop() - this.linkInterceptor.stop() + this.linkClickObserver.stop() this.formSubmitObserver.stop() } } @@ -180,7 +180,7 @@ export class FrameController // Form link click observer delegate willSubmitFormLinkToLocation(link: Element): boolean { - return this.shouldInterceptNavigation(link) + return link.closest("turbo-frame") == this.element && this.shouldInterceptNavigation(link) } submittedFormLinkToLocation(link: Element, _location: URL, form: HTMLFormElement): void { @@ -188,14 +188,14 @@ export class FrameController if (frame) form.setAttribute("data-turbo-frame", frame.id) } - // Link interceptor delegate + // Link click observer delegate - shouldInterceptLinkClick(element: Element, _url: string) { + willFollowLinkToLocation(element: Element) { return this.shouldInterceptNavigation(element) } - linkClickIntercepted(element: Element, url: string) { - this.navigateFrame(element, url) + followedLinkToLocation(element: Element, location: URL) { + this.navigateFrame(element, location.href) } // Form submit observer delegate diff --git a/src/core/frames/frame_redirector.ts b/src/core/frames/frame_redirector.ts index 93c9bbc5f..a226f0158 100644 --- a/src/core/frames/frame_redirector.ts +++ b/src/core/frames/frame_redirector.ts @@ -1,37 +1,40 @@ import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer" import { FrameElement } from "../../elements/frame_element" -import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { expandURL, getAction, locationIsVisitable } from "../url" +import { LinkClickObserver, LinkClickObserverDelegate } from "../../observers/link_click_observer" +import { Session } from "../session" -export class FrameRedirector implements LinkInterceptorDelegate, FormSubmitObserverDelegate { +export class FrameRedirector implements LinkClickObserverDelegate, FormSubmitObserverDelegate { + readonly session: Session readonly element: Element - readonly linkInterceptor: LinkInterceptor + readonly linkClickObserver: LinkClickObserver readonly formSubmitObserver: FormSubmitObserver - constructor(element: Element) { + constructor(session: Session, element: Element) { + this.session = session this.element = element - this.linkInterceptor = new LinkInterceptor(this, element) + this.linkClickObserver = new LinkClickObserver(this, element) this.formSubmitObserver = new FormSubmitObserver(this, element) } start() { - this.linkInterceptor.start() + this.linkClickObserver.start() this.formSubmitObserver.start() } stop() { - this.linkInterceptor.stop() + this.linkClickObserver.stop() this.formSubmitObserver.stop() } - shouldInterceptLinkClick(element: Element, _url: string) { + willFollowLinkToLocation(element: Element) { return this.shouldRedirect(element) } - linkClickIntercepted(element: Element, url: string) { + followedLinkToLocation(element: Element, url: URL) { const frame = this.findFrameElement(element) if (frame) { - frame.delegate.linkClickIntercepted(element, url) + frame.delegate.followedLinkToLocation(element, url) } } @@ -59,8 +62,12 @@ export class FrameRedirector implements LinkInterceptorDelegate, FormSubmitObser } private shouldRedirect(element: Element, submitter?: HTMLElement) { - const frame = this.findFrameElement(element, submitter) - return frame ? frame != element.closest("turbo-frame") : false + if (this.session.elementIsNavigatable(element) && (!submitter || this.session.submitterIsNavigatable(submitter))) { + const frame = this.findFrameElement(element, submitter) + return frame ? frame != element.closest("turbo-frame") : false + } else { + return false + } } private findFrameElement(element: Element, submitter?: HTMLElement) { diff --git a/src/core/frames/link_interceptor.ts b/src/core/frames/link_interceptor.ts deleted file mode 100644 index 65ff1066f..000000000 --- a/src/core/frames/link_interceptor.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { TurboClickEvent, TurboBeforeVisitEvent } from "../session" - -export interface LinkInterceptorDelegate { - shouldInterceptLinkClick(element: Element, url: string): boolean - linkClickIntercepted(element: Element, url: string): void -} - -export class LinkInterceptor { - readonly delegate: LinkInterceptorDelegate - readonly element: Element - private clickEvent?: Event - - constructor(delegate: LinkInterceptorDelegate, element: Element) { - this.delegate = delegate - this.element = element - } - - start() { - this.element.addEventListener("click", this.clickBubbled) - document.addEventListener("turbo:click", this.linkClicked) - document.addEventListener("turbo:before-visit", this.willVisit) - } - - stop() { - this.element.removeEventListener("click", this.clickBubbled) - document.removeEventListener("turbo:click", this.linkClicked) - document.removeEventListener("turbo:before-visit", this.willVisit) - } - - clickBubbled = (event: Event) => { - if (this.respondsToEventTarget(event.target)) { - this.clickEvent = event - } else { - delete this.clickEvent - } - } - - linkClicked = ((event: TurboClickEvent) => { - if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) { - if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) { - this.clickEvent.preventDefault() - event.preventDefault() - this.delegate.linkClickIntercepted(event.target, event.detail.url) - } - } - delete this.clickEvent - }) - - willVisit = ((_event: TurboBeforeVisitEvent) => { - delete this.clickEvent - }) - - respondsToEventTarget(target: EventTarget | null) { - const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null - return element && element.closest("turbo-frame, html") == this.element - } -} diff --git a/src/core/session.ts b/src/core/session.ts index 388277b16..7b1a0b8db 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -58,7 +58,7 @@ export class Session readonly scrollObserver = new ScrollObserver(this) readonly streamObserver = new StreamObserver(this) readonly formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement) - readonly frameRedirector = new FrameRedirector(document.documentElement) + readonly frameRedirector = new FrameRedirector(this, document.documentElement) drive = true enabled = true @@ -392,9 +392,9 @@ export class Session return this.elementIsNavigatable(element) } - elementIsNavigatable(element?: Element) { - const container = element?.closest("[data-turbo]") - const withinFrame = element?.closest("turbo-frame") + elementIsNavigatable(element: Element) { + const container = element.closest("[data-turbo]") + const withinFrame = element.closest("turbo-frame") // Check if Drive is enabled on the session or we're within a Frame. if (this.drive || withinFrame) { diff --git a/src/elements/frame_element.ts b/src/elements/frame_element.ts index 3e7676cad..ca9218c9b 100644 --- a/src/elements/frame_element.ts +++ b/src/elements/frame_element.ts @@ -1,6 +1,6 @@ import { FetchResponse } from "../http/fetch_response" import { Snapshot } from "../core/snapshot" -import { LinkInterceptorDelegate } from "../core/frames/link_interceptor" +import { LinkClickObserverDelegate } from "../observers/link_click_observer" import { FormSubmitObserverDelegate } from "../observers/form_submit_observer" export enum FrameLoadingStyle { @@ -10,7 +10,7 @@ export enum FrameLoadingStyle { export type FrameElementObservedAttribute = keyof FrameElement & ("disabled" | "complete" | "loading" | "src") -export interface FrameElementDelegate extends LinkInterceptorDelegate, FormSubmitObserverDelegate { +export interface FrameElementDelegate extends LinkClickObserverDelegate, FormSubmitObserverDelegate { connect(): void disconnect(): void completeChanged(): void diff --git a/src/observers/form_link_click_observer.ts b/src/observers/form_link_click_observer.ts index 6ff364de8..2c24c1468 100644 --- a/src/observers/form_link_click_observer.ts +++ b/src/observers/form_link_click_observer.ts @@ -6,20 +6,20 @@ export type FormLinkClickObserverDelegate = { } export class FormLinkClickObserver implements LinkClickObserverDelegate { - readonly linkInterceptor: LinkClickObserver + readonly linkClickObserver: LinkClickObserver readonly delegate: FormLinkClickObserverDelegate constructor(delegate: FormLinkClickObserverDelegate, element: HTMLElement) { this.delegate = delegate - this.linkInterceptor = new LinkClickObserver(this, element) + this.linkClickObserver = new LinkClickObserver(this, element) } start() { - this.linkInterceptor.start() + this.linkClickObserver.start() } stop() { - this.linkInterceptor.stop() + this.linkClickObserver.stop() } willFollowLinkToLocation(link: Element, location: URL, originalEvent: MouseEvent): boolean {