Skip to content

Commit

Permalink
Merge branch 'main' into pr/445
Browse files Browse the repository at this point in the history
* main:
  Add turbo:fetch-request-error event on frame and form network errors (hotwired#640)
  Return `Promise<void>` from `FrameElement.reload` (hotwired#661)
  Replace LinkInterceptor with LinkClickObserver (hotwired#412)
  Don't convert `data-turbo-stream` links to forms (hotwired#647)
  • Loading branch information
dhh committed Aug 3, 2022
2 parents 8c24548 + 14ae828 commit 52c9ced
Show file tree
Hide file tree
Showing 19 changed files with 128 additions and 116 deletions.
7 changes: 6 additions & 1 deletion src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FetchResponse } from "../../http/fetch_response"
import { expandURL } from "../url"
import { dispatch, getMetaContent } from "../../util"
import { StreamMessage } from "../streams/stream_message"
import { TurboFetchRequestErrorEvent } from "../session"

export interface FormSubmissionDelegate {
formSubmissionStarted(formSubmission: FormSubmission): void
Expand Down Expand Up @@ -161,7 +162,7 @@ export class FormSubmission {
}

if (this.requestAcceptsTurboStreamResponse(request)) {
headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ")
request.acceptResponseType(StreamMessage.contentType)
}
}

Expand Down Expand Up @@ -199,6 +200,10 @@ export class FormSubmission {

requestErrored(request: FetchRequest, error: Error) {
this.result = { success: false, error }
dispatch<TurboFetchRequestErrorEvent>("turbo:fetch-request-error", {
target: this.formElement,
detail: { request, error },
})
this.delegate.formSubmissionErrored(this, error)
}

Expand Down
14 changes: 13 additions & 1 deletion src/core/drive/visit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Adapter } from "../native/adapter"
import { FetchMethod, FetchRequest, FetchRequestDelegate } from "../../http/fetch_request"
import { FetchMethod, FetchRequest, FetchRequestDelegate, FetchRequestHeaders } from "../../http/fetch_request"
import { FetchResponse } from "../../http/fetch_response"
import { History } from "./history"
import { getAnchor } from "../url"
Expand All @@ -8,6 +8,7 @@ import { PageSnapshot } from "./page_snapshot"
import { Action, ResolvingFunctions } from "../types"
import { getHistoryMethodForAction, uuid } from "../../util"
import { PageView } from "./page_view"
import { StreamMessage } from "../streams/stream_message"

export interface VisitDelegate {
readonly adapter: Adapter
Expand Down Expand Up @@ -49,6 +50,7 @@ export type VisitOptions = {
restorationIdentifier?: string
shouldCacheSnapshot: boolean
frame?: string
acceptsStreamResponse: boolean
}

const defaultOptions: VisitOptions = {
Expand All @@ -58,6 +60,7 @@ const defaultOptions: VisitOptions = {
willRender: true,
updateHistory: true,
shouldCacheSnapshot: true,
acceptsStreamResponse: false,
}

export type VisitResponse = {
Expand Down Expand Up @@ -96,6 +99,7 @@ export class Visit implements FetchRequestDelegate {
response?: VisitResponse
scrolled = false
shouldCacheSnapshot = true
acceptsStreamResponse = false
snapshotHTML?: string
snapshotCached = false
state = VisitState.initialized
Expand All @@ -121,6 +125,7 @@ export class Visit implements FetchRequestDelegate {
willRender,
updateHistory,
shouldCacheSnapshot,
acceptsStreamResponse,
} = {
...defaultOptions,
...options,
Expand All @@ -136,6 +141,7 @@ export class Visit implements FetchRequestDelegate {
this.updateHistory = updateHistory
this.scrolled = !willRender
this.shouldCacheSnapshot = shouldCacheSnapshot
this.acceptsStreamResponse = acceptsStreamResponse
}

get adapter() {
Expand Down Expand Up @@ -335,6 +341,12 @@ export class Visit implements FetchRequestDelegate {

// Fetch request delegate

prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest) {
if (this.acceptsStreamResponse) {
request.acceptResponseType(StreamMessage.contentType)
}
}

requestStarted() {
this.startRequest()
}
Expand Down
46 changes: 32 additions & 14 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ 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"
import { isAction, Action } from "../types"
import { VisitOptions } from "../drive/visit"
import { TurboBeforeFrameRenderEvent } from "../session"
import { TurboBeforeFrameRenderEvent, TurboFetchRequestErrorEvent } from "../session"
import { StreamMessage } from "../streams/stream_message"

export type TurboFrameMissingEvent = CustomEvent<{ fetchResponse: FetchResponse }>

Expand All @@ -41,14 +42,14 @@ export class FrameController
FormSubmissionDelegate,
FrameElementDelegate,
FormLinkClickObserverDelegate,
LinkInterceptorDelegate,
LinkClickObserverDelegate,
ViewDelegate<FrameElement, Snapshot<FrameElement>>
{
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) => {}
Expand All @@ -61,13 +62,14 @@ export class FrameController
private frame?: FrameElement
readonly restorationIdentifier: string
private previousFrameElement?: FrameElement
private currentNavigationElement?: Element

constructor(element: FrameElement) {
this.element = element
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)
}
Expand All @@ -81,7 +83,7 @@ export class FrameController
this.loadSourceURL()
}
this.formLinkClickObserver.start()
this.linkInterceptor.start()
this.linkClickObserver.start()
this.formSubmitObserver.start()
}
}
Expand All @@ -91,7 +93,7 @@ export class FrameController
this.connected = false
this.appearanceObserver.stop()
this.formLinkClickObserver.stop()
this.linkInterceptor.stop()
this.linkClickObserver.stop()
this.formSubmitObserver.stop()
}
}
Expand Down Expand Up @@ -188,22 +190,22 @@ 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 {
const frame = this.findFrameElement(link)
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
Expand All @@ -225,8 +227,12 @@ export class FrameController

// Fetch request delegate

prepareHeadersForRequest(headers: FetchRequestHeaders, _request: FetchRequest) {
prepareHeadersForRequest(headers: FetchRequestHeaders, request: FetchRequest) {
headers["Turbo-Frame"] = this.id

if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) {
request.acceptResponseType(StreamMessage.contentType)
}
}

requestStarted(_request: FetchRequest) {
Expand All @@ -249,6 +255,10 @@ export class FrameController

requestErrored(request: FetchRequest, error: Error) {
console.error(error)
dispatch<TurboFetchRequestErrorEvent>("turbo:fetch-request-error", {
target: this.element,
detail: { request, error },
})
this.resolveVisitPromise()
}

Expand Down Expand Up @@ -348,7 +358,9 @@ export class FrameController

this.proposeVisitIfNavigatedWithAction(frame, element, submitter)

frame.src = url
this.withCurrentNavigationElement(element, () => {
frame.src = url
})
}

private proposeVisitIfNavigatedWithAction(frame: FrameElement, element: Element, submitter?: HTMLElement) {
Expand Down Expand Up @@ -524,6 +536,12 @@ export class FrameController
callback()
this.ignoredAttributes.delete(attributeName)
}

private withCurrentNavigationElement(element: Element, callback: () => void) {
this.currentNavigationElement = element
callback()
delete this.currentNavigationElement
}
}

function getFrameElementById(id: string | null) {
Expand Down
36 changes: 24 additions & 12 deletions src/core/frames/frame_redirector.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}

Expand Down Expand Up @@ -59,8 +62,17 @@ 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
const isNavigatable =
element instanceof HTMLFormElement
? this.session.submissionIsNavigatable(element, submitter)
: this.session.elementIsNavigatable(element)

if (isNavigatable) {
const frame = this.findFrameElement(element, submitter)
return frame ? frame != element.closest("turbo-frame") : false
} else {
return false
}
}

private findFrameElement(element: Element, submitter?: HTMLElement) {
Expand Down
57 changes: 0 additions & 57 deletions src/core/frames/link_interceptor.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export {
TurboBeforeRenderEvent,
TurboBeforeVisitEvent,
TurboClickEvent,
TurboFetchRequestErrorEvent,
TurboFrameLoadEvent,
TurboFrameRenderEvent,
TurboLoadEvent,
Expand Down
Loading

0 comments on commit 52c9ced

Please sign in to comment.