-
Notifications
You must be signed in to change notification settings - Fork 435
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The `HTMLFormElement, HTMLElement?` [data clump][] occurs in various places across the internals of the codebase. This commit extracts that pairing into an `HTMLFormSubmission` class that knows how to properly extract various pieces of HTML Form Submission context from the pairing, like the `method`, `action`, or `target` values (which can be overridden by `formmethod`, `formaction`, and `target`, respectively). With the new extraction various call sites can be simplified to delegate to the object, with the most impactful changes being made in the `FormSubmission` class (mostly changes to the `constructor` and the removal of various dynamic properties). [data clump]: https://refactoring.guru/smells/data-clumps
- Loading branch information
1 parent
da647a6
commit b76cba8
Showing
11 changed files
with
235 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { Action } from "../types" | ||
import { expandURL } from "../url" | ||
import { getAttribute, getVisitAction } from "../../util" | ||
import { FetchMethod, fetchMethodFromString } from "../../http/fetch_request" | ||
|
||
export enum FormEnctype { | ||
urlEncoded = "application/x-www-form-urlencoded", | ||
multipart = "multipart/form-data", | ||
plain = "text/plain", | ||
} | ||
|
||
export function formEnctypeFromString(encoding: string): FormEnctype { | ||
switch (encoding.toLowerCase()) { | ||
case FormEnctype.multipart: | ||
return FormEnctype.multipart | ||
case FormEnctype.plain: | ||
return FormEnctype.plain | ||
default: | ||
return FormEnctype.urlEncoded | ||
} | ||
} | ||
|
||
export class HTMLFormSubmission { | ||
readonly location: URL | ||
|
||
constructor(readonly form: HTMLFormElement, readonly submitter?: HTMLElement) { | ||
const url = expandURL(this.action) | ||
|
||
this.location = this.isSafe ? mergeFormDataEntries(url, [...this.body.entries()]) : url | ||
} | ||
|
||
get method(): string { | ||
return this.submitter?.getAttribute("formmethod") || this.form.getAttribute("method") || "" | ||
} | ||
|
||
get fetchMethod(): FetchMethod { | ||
return fetchMethodFromString(this.method.toLowerCase()) || FetchMethod.get | ||
} | ||
|
||
get target(): string | null { | ||
if (this.submitter?.hasAttribute("formtarget") || this.form.hasAttribute("target")) { | ||
return this.submitter?.getAttribute("formtarget") || this.form.getAttribute("target") | ||
} else { | ||
return null | ||
} | ||
} | ||
|
||
get action(): string { | ||
const formElementAction = typeof this.form.action === "string" ? this.form.action : null | ||
|
||
if (this.submitter?.hasAttribute("formaction")) { | ||
return this.submitter.getAttribute("formaction") || "" | ||
} else { | ||
return this.form.getAttribute("action") || formElementAction || "" | ||
} | ||
} | ||
|
||
get formData(): FormData { | ||
const formData = new FormData(this.form) | ||
const name = this.submitter?.getAttribute("name") | ||
const value = this.submitter?.getAttribute("value") | ||
|
||
if (name) { | ||
formData.append(name, value || "") | ||
} | ||
|
||
return formData | ||
} | ||
|
||
get enctype(): FormEnctype { | ||
return formEnctypeFromString(this.submitter?.getAttribute("formenctype") || this.form.enctype) | ||
} | ||
|
||
get body(): URLSearchParams | FormData { | ||
if (this.enctype == FormEnctype.urlEncoded || this.fetchMethod == FetchMethod.get) { | ||
const formDataAsStrings = [...this.formData].reduce((entries, [name, value]) => { | ||
return entries.concat(typeof value == "string" ? [[name, value]] : []) | ||
}, [] as [string, string][]) | ||
|
||
return new URLSearchParams(formDataAsStrings) | ||
} else { | ||
return this.formData | ||
} | ||
} | ||
|
||
get visitAction(): Action | null { | ||
return getVisitAction(this.submitter, this.form) | ||
} | ||
|
||
get frame(): string | null { | ||
return getAttribute("data-turbo-frame", this.submitter, this.form) | ||
} | ||
|
||
get isSafe(): boolean { | ||
return this.fetchMethod === FetchMethod.get | ||
} | ||
|
||
closest<E extends Element = Element>(selectors: string): E | null { | ||
return this.form.closest(selectors) | ||
} | ||
} | ||
|
||
function mergeFormDataEntries(url: URL, entries: [string, FormDataEntryValue][]): URL { | ||
const searchParams = new URLSearchParams() | ||
|
||
for (const [name, value] of entries) { | ||
if (value instanceof File) continue | ||
|
||
searchParams.append(name, value) | ||
} | ||
|
||
url.search = searchParams.toString() | ||
|
||
return url | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.