本章節探討如何傳送 HTML 表單:有或沒有檔案、額外欄位等等。
FormData 物件可以協助處理。正如你可能猜到的,它是用來表示 HTML 表單資料的物件。
建構函式為
let formData = new FormData([form]);
如果提供了 HTML form
元素,它會自動擷取其欄位。
FormData
的特別之處在於,例如 fetch
等網路方法可以接受 FormData
物件作為主體。它會編碼並以 Content-Type: multipart/form-data
傳送出去。
從伺服器觀點來看,這看起來像一般的表單提交。
傳送簡單表單
讓我們先傳送一個簡單表單。
正如你所見,這幾乎是一行式
<form id="formElem">
<input type="text" name="name" value="John">
<input type="text" name="surname" value="Smith">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch('/article/formdata/post/user', {
method: 'POST',
body: new FormData(formElem)
});
let result = await response.json();
alert(result.message);
};
</script>
在此範例中,未提供伺服器程式碼,因為這超出我們的範圍。伺服器接受 POST 要求並回覆「已儲存使用者」。
FormData 方法
我們可以使用方法修改 FormData
中的欄位
formData.append(name, value)
– 新增一個具有指定name
和value
的表單欄位,formData.append(name, blob, fileName)
– 新增一個欄位,就像<input type="file">
,第三個引數fileName
設定檔名(不是表單欄位名稱),就像使用者檔案系統中的檔名一樣,formData.delete(name)
– 移除具有指定name
的欄位,formData.get(name)
– 取得具有指定name
的欄位值,formData.has(name)
– 如果存在具有指定name
的欄位,則傳回true
,否則傳回false
技術上允許表單具有多個具有相同 name
的欄位,因此多次呼叫 append
會新增更多具有相同名稱的欄位。
還有一個方法 set
,其語法與 append
相同。不同之處在於 .set
會移除所有具有指定 name
的欄位,然後新增一個新欄位。因此,它確保只有一個具有此 name
的欄位,其餘部分就像 append
formData.set(name, value)
,formData.set(name, blob, fileName)
.
我們也可以使用 for..of
迴圈反覆處理 formData 欄位
let formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');
// List key/value pairs
for(let [name, value] of formData) {
alert(`${name} = ${value}`); // key1 = value1, then key2 = value2
}
傳送包含檔案的表單
表單總是作為 Content-Type: multipart/form-data
傳送,此編碼允許傳送檔案。因此,<input type="file">
欄位也會傳送,類似於一般的表單提交。
以下是此類表單的範例
<form id="formElem">
<input type="text" name="firstName" value="John">
Picture: <input type="file" name="picture" accept="image/*">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch('/article/formdata/post/user-avatar', {
method: 'POST',
body: new FormData(formElem)
});
let result = await response.json();
alert(result.message);
};
</script>
傳送包含 Blob 資料的表單
正如我們在章節 Fetch 中所見,傳送動態產生的二進位資料(例如影像)作為 Blob
非常容易。我們可以直接將其提供為 fetch
參數 body
。
然而,在實務上,通常比較方便將影像作為表單的一部分傳送,而不是單獨傳送,並加上其他欄位,例如「名稱」和其他元資料。
此外,伺服器通常更適合接受多部分編碼表單,而不是原始二進位資料。
此範例使用 FormData
將來自 <canvas>
的影像與其他一些欄位一起作為表單提交
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
<script>
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d');
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
};
async function submit() {
let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let formData = new FormData();
formData.append("firstName", "John");
formData.append("image", imageBlob, "image.png");
let response = await fetch('/article/formdata/post/image-form', {
method: 'POST',
body: formData
});
let result = await response.json();
alert(result.message);
}
</script>
</body>
請注意如何新增影像 Blob
formData.append("image", imageBlob, "image.png");
這與在表單中使用 <input type="file" name="image">
相同,且訪客從其檔案系統提交名為 "image.png"
(第 3 個引數) 的檔案,其資料為 imageBlob
(第 2 個引數)。
伺服器會讀取表單資料和檔案,就像一般的表單提交一樣。
摘要
FormData 物件用於擷取 HTML 表單,並使用 fetch
或其他網路方法提交表單。
我們可以從 HTML 表單建立 new FormData(form)
,或是在沒有表單的情況下建立一個物件,然後使用下列方法新增欄位:
formData.append(name, value)
formData.append(name, blob, fileName)
formData.set(name, value)
formData.set(name, blob, fileName)
讓我們在此注意兩個特殊情況:
set
方法會移除具有相同名稱的欄位,而append
則不會。這是兩者之間唯一的差異。- 若要傳送檔案,需要使用 3 個引數的語法,最後一個引數是檔案名稱,通常會從
<input type="file">
的使用者檔案系統取得。
其他方法包括:
formData.delete(name)
formData.get(name)
formData.has(name)
就這樣!
留言
<code>
標籤,若要插入多行程式碼,請將其包覆在<pre>
標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkr、jsbin、codepen…)