一个表单并不总是一个表单
随着开放Web应用程序的兴起,使用HTML forms而不是文字表单(literal forms for humans)日益普遍 - 越来越多的开发人员正在控制传输数据。
获得整体界面的控制
标准的HTML表单提交加载URL,这个URL是数据要发送的位置,这意味着浏览器窗口以整页加载进行导航。 避免整页加载可以通过隐藏闪烁和网络滞后来提供更平滑的体验。
许多现代用户界面只使用HTML表单来收集用户的输入。 当用户尝试发送数据时,应用程序将在后台异步控制和传输数据,只更新UI中需要更改的部分。
异步地发送任何数据被称为AJAX, 代表"Asynchronous JavaScript And XML"。
表单提交和AJAX请求之间的区别?
AJAX 技术主要依靠 XMLHttpRequest
(XHR) DOM 对象。它可以构造HTTP请求,并获取请求结果。
创建之初, XMLHttpRequest
被提出是打算将 XML 做为传输数据的格式。不过,JSON已经取代了XML,而且今天已经非常普遍了。
但是XML和JSON都不适合表单数据请求编码。 表单数据(application/x-www-form-urlencoded
)由URL编码的键/值对列表组成。为了传输二进制数据,HTTP请求被重新整合成multipart/form-data
。
如果您控制前端(在浏览器中执行的代码)和后端(在服务器上执行的代码),则可以发送JSON / XML并根据需要处理它们。
但是,如果你想使用第三方服务,这并不容易。 有些服务只接受表单数据。 也有使用表单数据更简单的情况。 如果数据是键/值对或原始二进制数据,现有的后端工具可以处理它,而不需要额外的代码。
那么如何发送这样的数据呢?
发送表单数据
一共有三种方式来发送表单数据:包括两种传统的方法和一种利用formData
对象的新方法.让我们仔细看一下:
在DOM中构建一个隐藏的iframe
异步发送表单数据的最古老方法是用DOM API构建表单,然后将其数据发送到隐藏的iframe
(HTML内联框架元素
。 要访问提交的结果,请获取iframe
的内容。
下面是个简单的例子:
所有操作都在下面这段脚本里:
// 首先创建一个用来发送数据的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 = "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi";
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);
}
手动构建XMLHttpRequest
XMLHttpRequest是进行HTTP请求的最安全和最可靠的方式。 要使用XMLHttpRequest发送表单数据,请通过对URL进行编码来准备数据,并遵守表单数据请求的具体内容。
这里有一个例子:
正如你所看到的,HTML并没有改变。 但是,JavaScript是完全不同的:
function sendData(data) {
var XHR = new XMLHttpRequest();
var urlEncodedData = "";
var urlEncodedDataPairs = [];
var name;
// Turn the data object into an array of URL-encoded key/value pairs.
for(name in data) {
urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
}
// Combine the pairs into a single string and replace all %-encoded spaces to
// the '+' character; matches the behaviour of browser form submissions.
urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g, '+');
// Define what happens on successful data submission
XHR.addEventListener('load', function(event) {
alert('Yeah! Data sent and response loaded.');
});
// Define what happens in case of error
XHR.addEventListener('error', function(event) {
alert('Oups! Something goes wrong.');
});
// Set up our request
XHR.open('POST', 'https://example.com/cors.php');
// Add the required HTTP header for form data POST requests
XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Finally, send our data.
XHR.send(urlEncodedData);
}
使用 XMLHttpRequest 和 the FormData object(表单对象)
手动建立一个HTTP请求可能是一个巨大的挑战(can be overwhelming)。 幸运的是,最近的XMLHttpRequest 规范提供了一种方便简单的方法来处理带有FormData对象的表单数据请求。
可以使用 FormData 对象来构建用于传输的表单数据,或者获取表单元素中的数据来管理它的发送方式。 请注意,FormData 对象是“只写”,这意味着您可以更改它们,但不检索其内容。
使用这个对象在Using FormData Objects中有详细的介绍,但是这里有两个例子:
向FormData对象中手动添加数据
你应该对那个HTML示例感到熟悉。
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', 'http://ucommbieber.unl.edu/CORS/cors.php');
// 发送这个formData对象,HTTP请求头会自动设置
XHR.send(FD);
}
使用绑定到表单元素上的 FormData
你也可以绑定一个 FormData 对象到一个
这段HTML是典型的情况:
但是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('Oups! Something goes wrong.');
});
// 我们设置了我们的请求
XHR.open("POST", "http://ucommbieber.unl.edu/CORS/cors.php");
// 发送的数据是由用户在表单中提供的
XHR.send(FD);
}
// 我们需要获取表单元素
var form = document.getElementById("myForm");
// 接管表单的提交事件
form.addEventListener("submit", function (event) {
event.preventDefault();
sendData();
});
});
发送二进制数据
如果你用来初始化formData对象的那个表单中包含了一个文件输入框(type=file的input元素),则在发送AJAX时,用户在这个文件输入框中选定的文件也会被发送,和正常的表单提交一样.而且即使你没有用表单初始化这个formData对象,你同样可以手动向这个formData对象中添加若干个二进制数据.
二进制数据的来源主要有三种:FileReader API,Canvas API,WebRTC API.不幸的是,在一些旧的浏览器中,我们没有能力访问二进制数据,或者需要一些很繁杂的解决办法才能实现.访问二进制数据已经超出了本文的介绍范围.如果你想知道更多关于FileReader API的知识,你可以阅读:如何在web应用程序中使用文件.
使用formData发送二进制数据非常简单,只需要调用append方法将你需要发送的File对象或者Blob对象添加进去.
在下面的例子中,我们使用了FileReader API来访问二进制数据,然后发送这个请求:
上面是一个普通的表单,包含一个文件输入框,下面是要执行的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]) {
// Start a new part in our body's request
data += "--" + boundary + "\r\n";
// Describe it as form data
data += 'content-disposition: form-data; '
// Define the name of the form data
+ 'name="' + file.dom.name + '"; '
// Provide the real name of the file
+ 'filename="' + file.dom.files[0].name + '"\r\n';
// And the MIME type of the file
data += 'Content-Type: ' + file.dom.files[0].type + '\r\n';
// There's a blank line between the metadata and the data
data += '\r\n';
// Append the binary data to our body's request
data += file.binary + '\r\n';
}
// Text data is simpler
// Start a new part in our body's request
data += "--" + boundary + "\r\n";
// Say it's form data, and name it
data += 'content-disposition: form-data; name="' + text.name + '"\r\n';
// There's a blank line between the metadata and the data
data += '\r\n';
// Append the text data to our body's request
data += text.value + "\r\n";
// Once we are done, "close" the body's request
data += "--" + boundary + "--";
// Define what happens on successful data submission
XHR.addEventListener('load', function(event) {
alert('Yeah! Data sent and response loaded.');
});
// Define what happens in case of error
XHR.addEventListener('error', function(event) {
alert('Oups! Something went wrong.');
});
// Set up our request
XHR.open('POST', 'https://example.com/cors.php');
// Add the required HTTP header to handle a multipart form data POST request
XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);
// And finally, send our data.
XHR.send(data);
}
// Access our form...
var form = document.getElementById("myForm");
// ...to take over the submit event
form.addEventListener('submit', function (event) {
event.preventDefault();
sendData();
});
});
总结
取决于不同的浏览器,通过JavaScript发送数据可能会很简单,也可能会很困难。FormData 对象是通用的答案, 所以请毫不犹豫的在旧浏览器上通过polyfill使用它。