Skip to content

Commit 090ea15

Browse files
authoredDec 2, 2022
Merge pull request #26 from remix-run/logan/formdata
feat(form-data): allow passing a form to FormData
2 parents 57acdf2 + 31a3c7d commit 090ea15

File tree

3 files changed

+112
-13
lines changed

3 files changed

+112
-13
lines changed
 

‎.changeset/real-news-divide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/web-form-data": patch
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

Lines changed: 36 additions & 12 deletions
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

Lines changed: 71 additions & 1 deletion
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
});
@@ -259,4 +259,74 @@ export const test = (test) => {
259259
"rename.md"
260260
);
261261
});
262+
263+
test("Should allow passing a form element", () => {
264+
/** @type {globalThis.HTMLFormElement} */
265+
let form;
266+
267+
if (typeof window === 'undefined') {
268+
/** @implements {globalThis.HTMLFormElement} */
269+
class FakeForm {
270+
get [Symbol.toStringTag]() {
271+
return "HTMLFormElement";
272+
}
273+
274+
toString() {
275+
return `<form></form>`;
276+
}
277+
278+
// @ts-ignore
279+
get elements() {
280+
return [
281+
{
282+
tagName: "INPUT",
283+
name: "inside",
284+
value: "",
285+
},
286+
{
287+
tagName: "INPUT",
288+
name: "outside",
289+
value: "",
290+
form: "my-form",
291+
},
292+
{
293+
tagName: "INPUT",
294+
name: "remember-me",
295+
value: "on",
296+
checked: true,
297+
}
298+
]
299+
}
300+
301+
get id() {
302+
return "my-form"
303+
}
304+
}
305+
306+
form = /** @type {globalThis.HTMLFormElement} */ (/** @type {unknown} */ (new FakeForm()))
307+
} else {
308+
form = document.createElement('form');
309+
let inside = document.createElement('input')
310+
let outside = document.createElement('input')
311+
let checkbox = document.createElement('input')
312+
313+
form.id = 'my-form'
314+
inside.name = 'inside'
315+
outside.name = 'outside'
316+
outside.setAttribute('form', 'my-form')
317+
checkbox.name = "remember-me"
318+
checkbox.type = 'checkbox'
319+
checkbox.checked = true;
320+
321+
form.appendChild(inside);
322+
form.appendChild(checkbox);
323+
document.body.appendChild(form);
324+
document.body.appendChild(outside);
325+
}
326+
327+
const formData = new FormData(form);
328+
assert.equal(formData.has("inside"), true)
329+
assert.equal(formData.has("outside"), true)
330+
assert.equal(formData.get("remember-me"), "on")
331+
})
262332
};

0 commit comments

Comments
 (0)