我可以安全地替换这个吗
document.getElementById("frm-Main").addEventListener("submit", (e) =>
{
e.preventDefault();
// const frmMain = e.target;
const frmMain = document.querySelector('#frm-Main');
let params = [];
const inputs = frmMain.getElementsByTagName("input");
console.log(inputs);
for (let i = 0; i < inputs.length; i++)
{
if (inputs[i].type == 'radio' && inputs[i].checked != true) continue;
if (inputs[i].type == 'checkbox' && inputs[i].checked != true) continue;
params.push({ id: inputs[i].name, value: inputs[i].value });
}
const selects = frmMain.getElementsByTagName("select");
for (let i = 0; i < selects.length; i++)
{
params.push({ id: selects[i].name, value: selects[i].value });
}
fetch(url,
{
method: 'POST',
body: JSON.stringify(params),
headers: { 'Content-type': 'application/json; charset=UTF-8' }
}).then(response => response.json()).then(data => console.log(data));
});
document.getElementById("frm-Main").addEventListener("submit", (e) =>
{
e.preventDefault();
const frmMain = document.querySelector('#frm-Main');
const formData = Object.fromEntries(new FormData(frmMain));
fetch(url,
{
method: 'POST',
body: JSON.stringify(formData),
headers: { 'Content-type': 'application/json; charset=UTF-8' }
}).then(response => response.json()).then(data => console.log(data));
});
在某些情况下,可以,但它不是一般安全的。这取决于表单数据是否包含具有重复名称的条目。例如,可以多选,也可以复选框组。您还可能在Safari中遇到与radiogroup API的变化相关的问题。
另外,FormData值可以是文件。您可能需要执行额外的自定义序列化来将这些值转换为JSON表示。
下面的交互式演示展示了在formdata包含具有通用名称的成员的场景中,使用Object.fromEntries
将如何无法正确工作(如果没有一些额外的预先转换)。它还显示了输出,因为如果作为多部分或urlencoded格式数据而不是JSON表示发送,它将出现在HTTP请求的正文中。
let controller;
form.addEventListener('submit', event => {
event.preventDefault();
update();
});
form.addEventListener('change', update);
update();
function renderEntry([ key, value ]) {
return `[ ${ JSON.stringify(key) }, ${ JSON.stringify(value) } ]`
}
async function update() {
controller?.abort();
let { signal } = controller = new AbortController;
let formData = new FormData(form);
let object = Object.fromEntries(formData);
let rawUSP = await new Response(new URLSearchParams(formData)).text();
let rawFD = await new Response(formData).text();
if (!signal.aborted) {
outputEntries.innerText = Array.from(formData, renderEntry).join('n');
outputFromEntries.innerText = JSON.stringify(object, null, 2);
outputRawUSP.innerText = rawUSP;
outputRawFD.innerText = rawFD;
}
}
code {
display: block;
padding: 1em 1ch;
white-space: pre;
}
dd, dl, dt {
all: unset;
display: block;
}
dt {
background-color: aquamarine;
font-weight: 700;
padding: 0 1ch;
}
.fields {
display: grid;
grid-gap: 0.5em;
grid-template-columns: min-content 1fr;
}
form {
display: grid;
grid-gap: 1em;
grid-template-columns: 1fr 1fr 1fr;
}
.field {
display: flex;
flex-direction: column;
}
.field:last-child {
grid-column: 1 / span 3;
}
output {
background: azure;
display: block;
font-family: monospace;
margin: 1em 0;
padding: 1ch;
}
<form id="form">
<div class="field">
<label for="foo">Foo</label>
<select multiple id="foo" name="foo">
<option selected>bar</option>
<option selected>baz</option>
</select>
</div>
<div class="field">
<label for="qux">Qux</label>
<input id="qux" name="qux" value="quux">
</div>
<fieldset>
<legend>Corge</legend>
<div class="fields">
<input checked id="grault" name="corge" type="checkbox" value="grault">
<label for="grault">grault</label>
<input id="garply" name="corge" type="checkbox" value="garply">
<label for="garply">garply</label>
<input checked id="waldo" name="corge" type="checkbox" value="waldo">
<label for="waldo">waldo</label>
</div>
</fieldset>
<div class="field">
<label for="output">Output</label>
<output id="output">
<dl>
<dt>FormData entries</dt>
<dd><code id="outputEntries"></code></dd>
<dt>Object.fromEntries</dt>
<dd><code id="outputFromEntries"></code></dd>
<dt>FormData application/x-www-form-urlencoded body</dt>
<dd><code id="outputRawUSP"></code></dd>
<dt>FormData multipart/form-data body</dt>
<dd><code id="outputRawFD"></code></dd>
</dl>
</output>
</div>
</form>
Object.fromEntries
结果缺少"bar"one_answers"grault"(在表单的初始状态下)的原因是,当使用条目时,每次在条目中遇到相同的名称时,它将导致覆盖前一个属性。对于给定的键,一个对象只能有一个自属性。
这并不意味着您永远不应该使用它来快速进行表单到对象的映射。如果您正在使用一个简单的表单,其中所有字段都是已知的单一文本值,那么就可以了。但是,您可能需要一个更健壮的解决方案,以避免以后在有人添加新字段时产生危险,这些字段确实需要映射到JSON中的其他表示。
如果创建一个更健壮的映射函数,Object.fromEntries
仍然是有用的。您只需要首先执行从表单到它的"json条目"的映射(考虑Object.fromEntries(getFormEntries(form))
)。FormData
类型不仅不能以对JSON有意义的方式建模数组-它也不知道其他JSON类型,如Number和Boolean。
中间的getFormEntries
函数可以写成生成[ name, value ]
对的生成器函数。在获取值时,您可以查看表单控件本身的类型,以确定如何在JSON中表示它们的值。例如,element.type === 'select-multiple'
告诉你基于element.selectedOptions
的JSON值,而element.type === 'number'
或element.type === 'range'
建议你想要element.valueAsNumber
而不是element.value
。
// Pseudo code! — a real implementation would need to account
// for RadioNodeList/its children, deciding whether checkboxes
// are booleans, string-valued, or arrays, and lots of other
// things — but the gist is something like this:
function formToJSON(form) {
return Object.fromEntries(getFormEntries(form));
}
function * getFormEntries(form) {
for (let control of form.elements) {
if (!control.disabled) {
yield [ control.name, getFormControlValue(control) ];
}
}
}
function getFormControlValue(control) {
switch (control.type) {
case 'number':
return control.valueAsNumber;
case 'select-multiple':
return Array.from(control.selectedOptions, option => option.value);
/* ... */
default:
return control.value;
}
}
您还需要排除属于disabled
的项。查看FormData
构造算法可以在这里提供线索,因为您可能想要复制它的规则来决定哪些表单控件模型可提交值—不同之处在于您想要以不同的方式派生这些值的表示。
注意,将表单值映射到JSON没有正确的答案。例如,我们应该如何表示<input type="date">
的值?我们可能想使用element.value
("2021-02-09"
),但也许我们更喜欢用element.valueAsDate.toISOString()
("2021-02-09T00:00:00.000Z"
)来表示它。答案取决于你的具体需求和你正在开发的合同。