Like Share Discussion Bookmark Smile

J.J. Huang   2019-06-06   Web   瀏覽次數:

Web - 第二十四章 | HTML 使用Java​Script發送表單

這邊文章開始前,必須說我跳過一篇文章How to build custom form widgets,因為我這章節的內容不感興趣,而且文章太過攏長讓學習意願大大降低,所以我跳過啦。

這邊要說的文章是Sending forms through Java​Script,身為工程師就是不管前後端還是資料庫⋯等等什麼都要會要懂,這是不可避免的,而這章講到的是使用Java​Script發送表單,直覺反應就是AJAX吧~哈,下面開始進入正題。

使用Java​Script發送表單

HTML表單可以聲明式地發送一個HTTP請求。但表單也可以通過JavaScript來準備一個用於發送的HTTP請求。本文探討如何做到這一點。

一個表單並不總是一個表單

隨著開放Web應用程序的興起,使用HTML forms而不是文字表單(literal forms for humans)日益普遍-越來越多的開發人員正在控制傳輸資料。

獲得整體界面的控制

標準的HTML表單提交加載URL,這個URL是資料要發送的位置,這意味著瀏覽器窗口以整頁加載進行導航。避免整頁加載可以通過隱藏閃爍和網路滯後來提供更平滑的體驗。

許多現代用戶界面只使用HTML表單來收集用戶的輸入。當用戶嘗試發送資料時,應用程序將在後台異步控制和傳輸資料,只更新UI中需要更改的部分。

異步地發送任何資料被稱為AJAX,代表”Asynchronous JavaScript And XML”。

表單提交和AJAX請求之間的區別?

AJAX技術主要依靠 XMLHttpRequest(XHR) DOM對象。它可以構造HTTP請求,並獲取請求結果。

注:老的AJAX技術可能不依賴XMLHttpRequest。例如, JSONP結合eval()函數。它可以使用,但是因其嚴重的安全問題不推薦使用。使用它的唯一原因是老的瀏覽器不支持XMLHttpRequest或JSON ,但是那些確實是非常古老的瀏覽器!避免使用這種技術。

建立之初, XMLHttpRequest被提出是打算將 XML 做為傳輸資料的格式。不過,JSON已經取代了XML,而且今天已經非常普遍了。

但是XML和JSON都不適合表單資料請求編碼。表單資料( application/x-www-form-urlencoded)由URL編碼的鍵/值對列表組成。為了傳輸二進制資料,HTTP請求被重新整合成multipart/form-data。

如果你控制前端(在瀏覽器中執行的代碼)和後端(在伺服器上執行的代碼),則可以發送JSON / XML並根據需要處理它們。

但是,如果你想使用第三方服務,這並不容易。有些服務只接受表單資料。也有使用表單資料更簡單的情況。如果資料是鍵/值對或原始二進制資料,現有的後端工具可以處理它,而不需要額外的代碼。


發送表單資料

一共有三種方式來發送表單資料:包括兩種傳統的方法和一種利用formData對象的新方法。

在DOM中構建一個隱藏的iframe

步發送表單資料的最古老方法是用DOM API構建表單,然後將其資料發送到隱藏的<iframe>。

警告:避免使用這項技術。有第三方服務的安全風險,因為它使你暴露在腳本注入攻擊中。如果你使用HTTPS,它可以影響同源策略,可以提供不可用的<iframe>內容。然而,該方法可能是你需要支持很古老的瀏覽器的唯一選擇。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<title>HTML 調試示例</title>
</head>
<body>

<button onclick="sendData({test:'ok'})">點擊我! </button>

<script type="text/javascript">
// 首先建立一個用來發送資料的iframe.
var iframe = document.createElement("iframe");
iframe.name = "myTarget";

// 必須把這個iframe插入當前文件.
window.addEventListener("load"function () {
iframe.style.display = "none";
document.body.appendChild(iframe);
});

// 下面這個函數是真正用來發送資料的.
// 它只有一個參數,一個包含鍵值對資料格式的對象.
function sendData(data) {
var name,
form = document.createElement("form"),
node = document.createElement("input");

// 註冊iframe的load事件處理程序,如果你需要在響應返回時執行一些操作的話.
iframe.addEventListener("load"function () {
alert("Yeah! Data sent.");
});

form.action = "https://morosedog.gitlab.io/";
form.target = iframe.name;

for(name in data) {
node.name = name;
node.value = data[name].toString();
form.appendChild(node.cloneNode());
}

// 表單元素需要添加到主文件中.
form.style.display = "none";
document.body.appendChild(form);

form.submit();

// 表單提交後,就可以刪除這個表單,不影響下次的資料發送.
document.body.removeChild(form);
}
</script>
</body>
</html>

手動構建XMLHttpRequest

XMLHttpRequest是進行HTTP請求的最安全和最可靠的方式。要使用XMLHttpRequest發送表單資料,請通過對URL進行編碼來準備資料,並遵守表單資料請求的具體內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<title>HTML 調試示例</title>
</head>
<body>

<button onclick="sendData({test:'ok'})">點擊我! </button>

<script type="text/javascript">
function sendData(data) {
var XHR = new XMLHttpRequest();
var urlEncodedData = "";
var urlEncodedDataPairs = [];
var name;

// 將資料對象轉換為URL編碼的鍵/值對數組。
for (name in data) {
urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
}

// 將這些對組合成一個字符串,並將所有%-encoded空格替換為'+'字符; 匹配瀏覽器表單提交的行為。
urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g'+');

// 定義成功提交資料時會發生什麼
XHR.addEventListener('load'function (event) {
alert('Yeah! Data sent and response loaded.');
});

// 定義發生錯誤時會發生什麼
XHR.addEventListener('error'function (event) {
alert('Oops! Something goes wrong.');
});

// 設置我們的請求
XHR.open('POST''https://morosedog.gitlab.io/');

// 為表單資料POST請求添加所需的HTTP標頭
XHR.setRequestHeader('Content-Type''application/x-www-form-urlencoded');

// 最後,發送我們的資料。
XHR.send(urlEncodedData);
}
</script>
</body>
</html>

注:使用XMLHttpRequest會受到同源策略的影響,如果你需要執行跨域請求,你需要熟悉一下CORS和HTTP訪問控制

使用XMLHttpRequest和the FormData object(表單對象)

手動建立一個HTTP請求可能是一個巨大的挑戰(can be overwhelming)。
XMLHttpRequest規範提供了一種方便簡單的方法來處理帶有FormData對象的表單資料請求。

可以使用FormData對象來構建用於傳輸的表單資料,或者獲取表單元素中的資料來管理它的發送方式。請注意,FormData 對像是“只寫”,這意味著你可以更改它們,但不檢索其內容。

使用這個對像在Using FormData Objects中有詳細的介紹,但是這裡有兩個例子:

  • 向FormData對像中手動添加資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<title>HTML 調試示例</title>
</head>
<body>

<button type="button" onclick="sendData({test:'ok'})">點擊我! </button>
<script type="text/javascript">
function sendData(data) {
var XHR = new XMLHttpRequest();
var FD = new FormData();

// 把我們的資料添加到這個FormData對像中
for(name in data) {
FD.append(name, data[name]);
}

// 定義資料成功發送並返回後執行的操作
XHR.addEventListener('load'function(event) {
alert('Yeah! Data sent and response loaded.');
});

// 定義發生錯誤時執行的操作
XHR.addEventListener('error'function(event) {
alert('Oups! Something goes wrong.');
});

// 設置請求地址和方法
XHR.open('POST''https://morosedog.gitlab.io/');

// 發送這個formData對象,HTTP請求頭會自動設置
XHR.send(FD);
}
</script>
</body>
</html>
  • 使用綁定到表單元素上的FormData
    你也可以綁定一個 FormData 對像到一個 <form>元素上。這會建立一個 FormData ,代表表單中包含的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<title>HTML 調試示例</title>
</head>
<body>

<form id="myForm">
<label for="myName">告诉我你的名字:</label>
<input id="myName" name="name" value="John">
<input type="submit" value="提交">
</form>

<script type="text/javascript">
window.addEventListener("load"function () {
function sendData() {
var XHR = new XMLHttpRequest();

// 我們把這個 FormData 和表單元素綁定在一起。
var FD = new FormData(form);

// 我們定義了資料成功發送時會發生的事。
XHR.addEventListener("load"function(event) {
alert(event.target.responseText);
});

// 我們定義了失敗的情形下會發生的事
XHR.addEventListener("error"function(event) {
alert('哎呀!出了問題。');
});

// 我們設置了我們的請求
XHR.open("POST""https://morosedog.gitlab.io/");

// 發送的資料是由用戶在表單中提供的
XHR.send(FD);
}

// 我們需要獲取表單元素
var form = document.getElementById("myForm");

// 接管表單的提交事件
form.addEventListener("submit"function (event) {
event.preventDefault();

sendData();
});
});
</script>
</body>
</html>

發送二進制資料

如果你用來初始化formData對象的那個表單中包含了一個文件輸入框(type=file的input元素),則在發送AJAX時,用戶在這個文件輸入框中選定的文件也會被發送,和正常的表單提交一樣。而且即使你沒有用表單初始化這個formData對象,你同樣可以手動向這個formData對像中添加若干個二進制資料。

使用formData發送二進制資料非常簡單,只需要調用append方法将你需要发送的File对象或者Blob对象添加进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<title>HTML 調試示例</title>
</head>
<body>
<form id="myForm">
<p>
<label for="i1">文本資料:</label>
<input id="i1" name="myText" value="一些文本資料">
</p>
<p>
<label for="i2">文件資料:</label>
<input id="i2" name="myFile" type="file">
</p>
<button>提交</button>
</form>
<script type="text/javascript">
// 因為我們想獲取DOM節點,
// 我們在頁面加載時初始化我們的腳本.
window.addEventListener('load', function () {

// 這些變量用於存儲表單資料
var text = document.getElementById("i1");
var file = {
dom : document.getElementById("i2"),
binary : null
};

// 使用 FileReader API 獲取文件內容
var reader = new FileReader();

// 因為 FileReader 是異步的, 會在完成讀取文件時存儲結果
reader.addEventListener("load", function () {
file.binary = reader.result;
});

// 頁面加載時, 如果一個文件已經被選擇, 那麼讀取該文件.
if(file.dom.files[0]) {
reader.readAsBinaryString(file.dom.files[0]);
}

// 如果沒有,一旦用戶選擇了它,就讀取文件。
file.dom.addEventListener("change", function () {
if(reader.readyState === FileReader.LOADING) {
reader.abort();
}

reader.readAsBinaryString(file.dom.files[0]);
});

// 在我們的主函數中發送資料
function sendData() {
// 如果存在被選擇的文件,等待它讀取完成
// 如果沒有, 延遲函數的執行
if(!file.binary && file.dom.files.length > 0) {
setTimeout(sendData, 10);
return;
}

// 要構建我們的多部分錶單資料請求,
// 我們需要一個XMLHttpRequest 實例
var XHR = new XMLHttpRequest();

// 我們需要一個分隔符來定義請求的每一部分。
var boundary = "blob";

// 將我們的請求主體存儲於一個字符串中
var data = "";

// 所以,如果用戶已經選擇了一個文件
if (file.dom.files[0]) {
// 在請求體中開始新的一部分把它描述成表單資料
data += "--" + boundary + "\r\n";

//
data += 'content-disposition: form-data; '
// 定義表單資料的名稱
+ 'name="' + file.dom.name + '"; '
// 提供文件的真實名字
+ 'filename="' + file.dom.files[0].name + '"\r\n';
// 和文件的MIME類型
data += 'Content-Type: ' + file.dom.files[0].type + '\r\n';

// 元資料和資料之間有一條空行。
data += '\r\n';

// 添加二進制資料到請求體中
data += file.binary + '\r\n';
}

// 文本資料是簡單的
// 開始一個新的部分在請求體中
data += "--" + boundary + "\r\n";

// 說它是表單資料,並命名它
data += 'content-disposition: form-data; name="' + text.name + '"\r\n';
// 元資料和資料之間有一條空行。
data += '\r\n';

// 添加文本資料到請求體中
data += text.value + "\r\n";

// 一旦完成,關閉請求體
data += "--" + boundary + "--";

// 定義成功提交資料執行的語句
XHR.addEventListener('load', function(event) {
alert('✌!資料已發送且響應已加載。');
});

// 定義發生錯誤時做的事
XHR.addEventListener('error', function(event) {
alert('哎呀!出了問題。');
});

// 建立請求
XHR.open('POST', 'https://morosedog.gitlab.io/');

// 添加需要的HTTP報頭來處理多部分錶單資料POST請求
XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);

// 最後,發送資料。
XHR.send(data);
}

// 訪問表單…
var form = document.getElementById("myForm");

// ……接管提交事件
form.addEventListener('submit', function (event) {
event.preventDefault();
sendData();
});
});
</script>
</body>
</html>

總結

取決於不同的瀏覽器,通過JavaScript發送資料可能會很簡單,也可能會很困難。FormData對像是通用的答案,所以請毫不猶豫的在舊瀏覽器上通過polyfill使用它。

這邊轉載了大量的文章和內容,但是每個部分我都有實際去做過,並了解過。這邊轉載主要是做筆記記錄。

註:以上參考了
MDN web docsSending forms through Java​Script 文章。