uppy/packages/@uppy/form/src/index.ts
Merlijn Vos 88d508f7bf
TS strict mode (#5258)
Co-authored-by: Antoine du Hamel <antoine@transloadit.com>
2024-06-20 16:54:10 +02:00

182 lines
5.5 KiB
TypeScript

import BasePlugin, { type DefinePluginOpts } from '@uppy/core/lib/BasePlugin.js'
import findDOMElement from '@uppy/utils/lib/findDOMElement'
import toArray from '@uppy/utils/lib/toArray'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore untyped
import getFormData from 'get-form-data'
import type { UIPluginOptions, Uppy, UppyEventMap } from '@uppy/core'
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
import packageJson from '../package.json'
type Result<M extends Meta, B extends Body> = Parameters<
UppyEventMap<M, B>['complete']
>[0]
export interface FormOptions extends UIPluginOptions {
resultName?: string
getMetaFromForm?: boolean
addResultToForm?: boolean
submitOnSuccess?: boolean
triggerUploadOnSubmit?: boolean
}
const defaultOptions = {
resultName: 'uppyResult',
getMetaFromForm: true,
addResultToForm: true,
submitOnSuccess: false,
triggerUploadOnSubmit: false,
}
type Opts = DefinePluginOpts<FormOptions, keyof typeof defaultOptions>
function assertHTMLFormElement(input: Node | null): HTMLFormElement {
if (input == null || input.nodeName !== 'FORM') {
throw new Error('ASSERTION FAILED: the target is not a <form> element', {
cause: input,
})
}
return input as any
}
export default class Form<M extends Meta, B extends Body> extends BasePlugin<
Opts,
M,
B
> {
static VERSION = packageJson.version
#form!: HTMLFormElement
/**
* Unfortunately Uppy isn't a state machine in which we can guarantee it's
* currently in one state and one state only so we use this completed property which is set on `upload-success'.
*/
#completed = false
constructor(uppy: Uppy<M, B>, opts?: FormOptions) {
super(uppy, { ...defaultOptions, ...opts })
this.type = 'acquirer'
this.id = this.opts.id || 'Form'
this.handleFormSubmit = this.handleFormSubmit.bind(this)
this.handleUploadStart = this.handleUploadStart.bind(this)
this.handleSuccess = this.handleSuccess.bind(this)
this.addResultToForm = this.addResultToForm.bind(this)
this.getMetaFromForm = this.getMetaFromForm.bind(this)
}
handleUploadStart(): void {
this.#completed = false
if (this.opts.getMetaFromForm) {
this.getMetaFromForm()
}
}
handleSuccess(result: Result<M, B>): void {
this.#completed = true
if (this.opts.addResultToForm) {
this.addResultToForm(result)
}
if (this.opts.submitOnSuccess) {
this.#form.requestSubmit()
}
}
handleFormSubmit(ev: Event): void {
if (this.opts.triggerUploadOnSubmit && !this.#completed) {
ev.preventDefault()
const elements = toArray((ev.target as HTMLFormElement).elements)
const disabledByUppy: HTMLButtonElement[] = []
elements.forEach((el) => {
const isButton =
el.tagName === 'BUTTON' ||
(el.tagName === 'INPUT' &&
(el as HTMLButtonElement).type === 'submit')
if (isButton && !(el as HTMLButtonElement).disabled) {
;(el as HTMLButtonElement).disabled = true // eslint-disable-line no-param-reassign
disabledByUppy.push(el as HTMLButtonElement)
}
})
this.uppy
.upload()
.then(
() => {
disabledByUppy.forEach((button) => {
button.disabled = false // eslint-disable-line no-param-reassign
})
},
(err) => {
disabledByUppy.forEach((button) => {
button.disabled = false // eslint-disable-line no-param-reassign
})
return Promise.reject(err)
},
)
.catch((err) => {
this.uppy.log(err.stack || err.message || err)
})
}
}
addResultToForm(result: Result<M, B>): void {
this.uppy.log('[Form] Adding result to the original form:')
this.uppy.log(result)
let resultInput: HTMLInputElement | null = this.#form.querySelector(
`[name="${this.opts.resultName}"]`,
)
if (resultInput) {
// Append new result to the previous result array.
// If the previous result is empty, or not an array,
// set it to an empty array.
let updatedResult
try {
updatedResult = JSON.parse(resultInput.value)
} catch (err) {
// Nothing, since we check for array below anyway
}
if (!Array.isArray(updatedResult)) {
updatedResult = []
}
updatedResult.push(result)
resultInput.value = JSON.stringify(updatedResult)
return
}
resultInput = document.createElement('input')
resultInput.name = this.opts.resultName
resultInput.type = 'hidden'
resultInput.value = JSON.stringify([result])
this.#form.appendChild(resultInput)
}
getMetaFromForm(): void {
const formMeta = getFormData(this.#form)
// We want to exclude meta the the Form plugin itself has added
// See https://github.com/transloadit/uppy/issues/1637
delete formMeta[this.opts.resultName]
this.uppy.setMeta(formMeta)
}
install(): void {
this.#form = assertHTMLFormElement(findDOMElement(this.opts.target))
this.#form.addEventListener('submit', this.handleFormSubmit)
this.uppy.on('upload', this.handleUploadStart)
this.uppy.on('complete', this.handleSuccess)
}
uninstall(): void {
this.#form.removeEventListener('submit', this.handleFormSubmit)
this.uppy.off('upload', this.handleUploadStart)
this.uppy.off('complete', this.handleSuccess)
}
}