Skip to content

Commit fd274bd

Browse files
feat(form-data): allow passing a form to FormData (#84)
* feat(form-data): allow passing a form to FormData Signed-off-by: Logan McAnsh <[email protected]> * chore: update return Signed-off-by: Logan McAnsh <[email protected]> * test: add test Signed-off-by: Logan McAnsh <[email protected]> * Create real-news-divide.md * chore: use setAttribute Signed-off-by: Logan McAnsh <[email protected]> * chore: add jsdom Signed-off-by: Logan McAnsh <[email protected]> * fix: setup entries before going through form Signed-off-by: Logan McAnsh <[email protected]> * test: setup jsdom in caller file Signed-off-by: Logan McAnsh <[email protected]> * chore: left over debug * chore: remove jsdom, pass fake form object Signed-off-by: Logan McAnsh <[email protected]> * test: update fake form Signed-off-by: Logan McAnsh <[email protected]> * chore: use isHTMLFormElement function Signed-off-by: Logan McAnsh <[email protected]> * chore: use real HTMLFormElement in the browser Signed-off-by: Logan McAnsh <[email protected]> * chore: fix changeset --------- Signed-off-by: Logan McAnsh <[email protected]> Co-authored-by: Logan McAnsh <[email protected]>
1 parent 3c20536 commit fd274bd

File tree

3 files changed

+112
-13
lines changed

3 files changed

+112
-13
lines changed

.changeset/real-news-divide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@web-std/form-data": minor
3+
---
4+
5+
allow passing a `form` to `FormData`. implementation taken from https://github.com/tchak/remix-router-turbo/blob/main/test/setup-test-env.ts, thank you @tchak

packages/form-data/src/form-data.js

+36-12
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@ export class FormData {
66
* @param {HTMLFormElement} [form]
77
*/
88
constructor(form) {
9-
if (form !== undefined) {
10-
const error = isHTMLFormElement(form)
11-
? new TypeError(
12-
"FormData constructor: HTMLFormElement parameter is not supported, if you need it submit an issue"
13-
)
14-
: new TypeError(
15-
"FormData constructor: Argument 1 does not implement interface HTMLFormElement."
16-
)
17-
18-
throw error
19-
}
20-
219
/**
2210
* @private
2311
* @readonly
@@ -26,6 +14,24 @@ export class FormData {
2614
this._entries = []
2715

2816
Object.defineProperty(this, "_entries", { enumerable: false })
17+
18+
if (isHTMLFormElement(form)) {
19+
for (const element of form.elements) {
20+
if (isSelectElement(element)) {
21+
for (const option of element.options) {
22+
if (option.selected) {
23+
this.append(element.name, option.value);
24+
}
25+
}
26+
} else if (
27+
isInputElement(element) &&
28+
(element.checked || !["radio", "checkbox"].includes(element.type)) &&
29+
element.name
30+
) {
31+
this.append(element.name, element.value);
32+
}
33+
}
34+
}
2935
}
3036
get [Symbol.toStringTag]() {
3137
return "FormData"
@@ -312,3 +318,21 @@ const BlobFile = class File {
312318
const panic = error => {
313319
throw error
314320
}
321+
322+
/**
323+
*
324+
* @param {Element} element
325+
* @returns {element is HTMLSelectElement}
326+
*/
327+
function isSelectElement(element) {
328+
return element.tagName === "SELECT";
329+
}
330+
331+
/**
332+
*
333+
* @param {Element} element
334+
* @returns {element is HTMLInputElement}
335+
*/
336+
function isInputElement(element) {
337+
return element.tagName === "INPUT" || element.tagName === "TEXTAREA";
338+
}

packages/form-data/test/form-data.spec.js

+71-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { assert } from "./test.js"
77
* @param {import('./test').Test} test
88
*/
99
export const test = test => {
10-
test("test baisc", async () => {
10+
test("test basic", async () => {
1111
assert.equal(typeof FormData, "function")
1212
assert.isEqual(typeof lib.FormData, "function")
1313
})
@@ -248,4 +248,74 @@ export const test = test => {
248248
"rename.md"
249249
)
250250
})
251+
252+
test("Should allow passing a form element", () => {
253+
/** @type {globalThis.HTMLFormElement} */
254+
let form;
255+
256+
if (typeof window === 'undefined') {
257+
/** @implements {globalThis.HTMLFormElement} */
258+
class FakeForm {
259+
get [Symbol.toStringTag]() {
260+
return "HTMLFormElement";
261+
}
262+
263+
toString() {
264+
return `<form></form>`;
265+
}
266+
267+
// @ts-ignore
268+
get elements() {
269+
return [
270+
{
271+
tagName: "INPUT",
272+
name: "inside",
273+
value: "",
274+
},
275+
{
276+
tagName: "INPUT",
277+
name: "outside",
278+
value: "",
279+
form: "my-form",
280+
},
281+
{
282+
tagName: "INPUT",
283+
name: "remember-me",
284+
value: "on",
285+
checked: true,
286+
}
287+
]
288+
}
289+
290+
get id() {
291+
return "my-form"
292+
}
293+
}
294+
295+
form = /** @type {globalThis.HTMLFormElement} */ (/** @type {unknown} */ (new FakeForm()))
296+
} else {
297+
form = document.createElement('form');
298+
let inside = document.createElement('input')
299+
let outside = document.createElement('input')
300+
let checkbox = document.createElement('input')
301+
302+
form.id = 'my-form'
303+
inside.name = 'inside'
304+
outside.name = 'outside'
305+
outside.setAttribute('form', 'my-form')
306+
checkbox.name = "remember-me"
307+
checkbox.type = 'checkbox'
308+
checkbox.checked = true;
309+
310+
form.appendChild(inside);
311+
form.appendChild(checkbox);
312+
document.body.appendChild(form);
313+
document.body.appendChild(outside);
314+
}
315+
316+
const formData = new FormData(form);
317+
assert.equal(formData.has("inside"), true)
318+
assert.equal(formData.has("outside"), true)
319+
assert.equal(formData.get("remember-me"), "on")
320+
})
251321
}

0 commit comments

Comments
 (0)