表单,FormData 对象

    上面代码就是一个简单的表单,包含三个控件:用户名输入框、密码输入框和提交按钮。

    用户点击“提交”按钮,每一个控件都会生成一个键值对,键名是控件的name属性,键值是控件的value属性,键名和键值之间由等号连接。比如,用户名输入框的name属性是user_namevalue属性是用户输入的值,假定是“张三”,提交到服务器的时候,就会生成一个键值对user_name=张三

    所有的键值对都会提交到服务器。但是,提交的数据格式跟<form>元素的method属性有关。该属性指定了提交数据的 HTTP 方法。如果是 GET 方法,所有键值对会以 URL 的查询字符串形式,提交到服务器,比如/handling-page?user_name=张三&user_passwd=123&submit_button=提交。下面就是 GET 请求的 HTTP 头信息。

    1. GET /handling-page?user_name=张三&user_passwd=123&submit_button=提交
    2. Host: example.com

    如果是 POST 方法,所有键值对会连接成一行,作为 HTTP 请求的数据体发送到服务器,比如user_name=张三&user_passwd=123&submit_button=提交。下面就是 POST 请求的头信息。

    1. POST /handling-page HTTP/1.1
    2. Host: example.com
    3. Content-Type: application/x-www-form-urlencoded
    4. Content-Length: 74
    5. user_name=张三&user_passwd=123&submit_button=提交

    注意,实际提交的时候,只要键值不是 URL 的合法字符(比如汉字“张三”和“提交”),浏览器会自动对其进行编码。

    点击submit控件,就可以提交表单。

    1. <form>
    2. <input type="submit" value="提交">
    3. </form>

    上面表单就包含一个submit控件,点击这个控件,浏览器就会把表单数据向服务器提交。

    注意,表单里面的<button>元素如果没有用type属性指定类型,那么默认就是submit控件。

    1. <form>
    2. <button>提交</button>
    3. </form>

    上面表单的<button>元素,点击以后也会提交表单。

    除了点击submit控件提交表单,还可以用表单元素的submit()方法,通过脚本提交表单。

    1. formElement.submit();

    表单元素的reset()方法可以重置所有控件的值(重置为默认值)。

    1. formElement.reset()

    FormData 对象

    表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。但是有时候,我们希望通过脚本完成过程,构造和编辑表单键值对,然后通过XMLHttpRequest.send()方法发送。浏览器原生提供了 FormData 对象来完成这项工作。

    FormData 首先是一个构造函数,用来生成实例。

    1. var formdata = new FormData(form);

    FormData()构造函数的参数是一个表单元素,这个参数是可选的。如果省略参数,就表示一个空的表单,否则就会处理表单元素里面的键值对。

    下面是一个表单。

    1. <form id="myForm" name="myForm">
    2. <div>
    3. <label for="username">用户名:</label>
    4. <input type="text" id="username" name="username">
    5. </div>
    6. <div>
    7. <label for="useracc">账号:</label>
    8. <input type="text" id="useracc" name="useracc">
    9. </div>
    10. <div>
    11. <label for="userfile">上传文件:</label>
    12. <input type="file" id="userfile" name="userfile">
    13. </div>
    14. <input type="submit" value="Submit!">
    15. </form>

    我们用 FormData 对象处理上面这个表单。

    1. var myForm = document.getElementById('myForm');
    2. var formData = new FormData(myForm);
    3. // 获取某个控件的值
    4. formData.get('username') // ""
    5. // 设置某个控件的值
    6. formData.set('username', '张三');
    7. formData.get('username') // "张三"

    实例方法

    FormData 提供以下实例方法。

    • FormData.get(key):获取指定键名对应的键值,参数为键名。如果有多个同名的键值对,则返回第一个键值对的键值。
    • FormData.getAll(key):返回一个数组,表示指定键名对应的所有键值。如果有多个同名的键值对,数组会包含所有的键值。
    • FormData.set(key, value):设置指定键名的键值,参数为键名。如果键名不存在,会添加这个键值对,否则会更新指定键名的键值。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
    • FormData.delete(key):删除一个键值对,参数为键名。
    • FormData.append(key, value):添加一个键值对。如果键名重复,则会生成两个相同键名的键值对。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
    • FormData.has(key):返回一个布尔值,表示是否具有该键名的键值对。
    • FormData.keys():返回一个遍历器对象,用于for...of循环遍历所有的键名。
    • FormData.values():返回一个遍历器对象,用于for...of循环遍历所有的键值。
    • FormData.entries():返回一个遍历器对象,用于for...of循环遍历所有的键值对。如果直接用for...of循环遍历 FormData 实例,默认就会调用这个方法。

    下面是get()getAll()set()append()方法的例子。

    1. var formData = new FormData();
    2. formData.set('username', '张三');
    3. formData.append('username', '李四');
    4. formData.get('username') // "张三"
    5. formData.getAll('username') // ["张三", "李四"]
    6. formData.append('userpic[]', myFileInput.files[0], 'user1.jpg');
    7. formData.append('userpic[]', myFileInput.files[1], 'user2.jpg');

    下面是遍历器的例子。

    1. var formData = new FormData();
    2. formData.append('key1', 'value1');
    3. formData.append('key2', 'value2');
    4. for (var key of formData.keys()) {
    5. console.log(key);
    6. }
    7. // "key1"
    8. // "key2"
    9. for (var value of formData.values()) {
    10. console.log(value);
    11. }
    12. // "value1"
    13. // "value2"
    14. for (var pair of formData.entries()) {
    15. console.log(pair[0] + ': ' + pair[1]);
    16. }
    17. // key1: value1
    18. // key2: value2
    19. // 等同于遍历 formData.entries()
    20. for (var pair of formData) {
    21. console.log(pair[0] + ': ' + pair[1]);
    22. }
    23. // key1: value1
    24. // key2: value2

    自动校验

    表单提交的时候,浏览器允许开发者指定一些条件,它会自动验证各个表单控件的值是否符合条件。

    1. <!-- 必填 -->
    2. <input required>
    3. <!-- 必须符合正则表达式 -->
    4. <!-- 字符串长度必须为6个字符 -->
    5. <input minlength="6" maxlength="6">
    6. <!-- 数值必须在1到10之间 -->
    7. <input type="number" min="1" max="10">
    8. <!-- 必须填入 Email 地址 -->
    9. <input type="email">
    10. <!-- 必须填入 URL -->
    11. <input type="URL">
    1. input:invalid {
    2. border-color: red;
    3. }
    4. input,
    5. input:valid {
    6. border-color: #ccc;
    7. }

    除了提交表单的时候,浏览器自动校验表单,还可以手动触发表单的校验。表单元素和表单控件都有checkValidity()方法,用于手动触发校验。

    checkValidity()方法返回一个布尔值,true表示通过校验,false表示没有通过校验。因此,提交表单可以封装为下面的函数。

    1. function submitForm(action) {
    2. var form = document.getElementById('form');
    3. form.action = action;
    4. if (form.checkValidity()) {
    5. form.submit();
    6. }
    7. }

    willValidate 属性

    控件元素的willValidate属性是一个布尔值,表示该控件是否会在提交时进行校验。

    1. // HTML 代码如下
    2. // <form novalidate>
    3. // <input id="name" name="name" required />
    4. // </form>
    5. var input = document.querySelector('#name');
    6. input.willValidate // true

    validationMessage 属性

    控件元素的validationMessage属性返回一个字符串,表示控件不满足校验条件时,浏览器显示的提示文本。以下两种情况,该属性返回空字符串。

    • 该控件不会在提交时自动校验
    • 该控件满足校验条件
    1. // HTML 代码如下
    2. // <form><input type="text" required></form>
    3. document.querySelector('form input').validationMessage
    4. // "请填写此字段。"

    下面是另一个例子。

    1. var myInput = document.getElementById('myinput');
    2. if (!myInput.checkValidity()) {
    3. document.getElementById('prompt').innerHTML = myInput.validationMessage;
    4. }

    控件元素的setCustomValidity()方法用来定制校验失败时的报错信息。它接受一个字符串作为参数,该字符串就是定制的报错信息。如果参数为空字符串,则上次设置的报错信息被清除。

    这个方法可以替换浏览器内置的表单验证报错信息,参数就是要显示的报错信息。

    1. <form action="somefile.php">
    2. <input
    3. type="text"
    4. name="username"
    5. placeholder="Username"
    6. pattern="[a-z]{1,15}"
    7. id="username"
    8. >
    9. <input type="submit">
    10. </form>

    上面的表单输入框,要求只能输入小写字母,且不得超过15个字符。如果输入不符合要求(比如输入“ABC”),提交表单的时候,Chrome 浏览器会弹出报错信息“Please match the requested format.”,禁止表单提交。下面使用setCustomValidity()方法替换掉报错信息。

    1. var input = document.getElementById('username');
    2. input.oninvalid = function (event) {
    3. event.target.setCustomValidity(
    4. '用户名必须是小写字母,不能为空,最长不超过15个字符'
    5. );
    6. }

    上面代码中,setCustomValidity()方法是在invalid事件的监听函数里面调用。该方法也可以直接调用,这时如果参数不为空字符串,浏览器就会认为该控件没有通过校验,就会立刻显示该方法设置的报错信息。

    1. /* HTML 代码如下
    2. <form>
    3. <p><input type="file" id="fs"></p>
    4. <p><input type="submit"></p>
    5. </form>
    6. */
    7. document.getElementById('fs').onchange = checkFileSize;
    8. function checkFileSize() {
    9. var fs = document.getElementById('fs');
    10. var files = fs.files;
    11. if (files.length > 0) {
    12. if (files[0].size > 75 * 1024) {
    13. fs.setCustomValidity('文件不能大于 75KB');
    14. return;
    15. }
    16. }
    17. fs.setCustomValidity('');
    18. }

    上面代码一旦发现文件大于 75KB,就会设置校验失败,同时给出自定义的报错信息。然后,点击提交按钮时,就会显示报错信息。这种校验失败是不会自动消除的,所以如果所有文件都符合条件,要将报错信息设为空字符串,手动消除校验失败的状态。

    validity 属性

    控件元素的属性validity属性返回一个ValidityState对象,包含当前校验状态的信息。

    该对象有以下属性,全部为只读属性。

    • ValidityState.badInput:布尔值,表示浏览器是否不能将用户的输入转换成正确的类型,比如用户在数值框里面输入字符串。
    • ValidityState.customError:布尔值,表示是否已经调用setCustomValidity()方法,将校验信息设置为一个非空字符串。
    • ValidityState.patternMismatch:布尔值,表示用户输入的值是否不满足模式的要求。
    • ValidityState.rangeOverflow:布尔值,表示用户输入的值是否大于最大范围。
    • ValidityState.rangeUnderflow:布尔值,表示用户输入的值是否小于最小范围。
    • ValidityState.stepMismatch:布尔值,表示用户输入的值不符合步长的设置(即不能被步长值整除)。
    • ValidityState.tooLong:布尔值,表示用户输入的字数超出了最长字数。
    • ValidityState.tooShort:布尔值,表示用户输入的字符少于最短字数。
    • ValidityState.typeMismatch:布尔值,表示用户填入的值不符合类型要求(主要是类型为 Email 或 URL 的情况)。
    • ValidityState.valid:布尔值,表示用户是否满足所有校验条件。
    • ValidityState.valueMissing:布尔值,表示用户没有填入必填的值。

    下面是一个例子。

    1. var input = document.getElementById('myinput');
    2. if (input.validity.valid) {
    3. console.log('通过校验');
    4. } else {
    5. console.log('校验失败');
    6. }

    下面是另外一个例子。

    1. var txt = '';
    2. if (document.getElementById('myInput').validity.rangeOverflow) {
    3. txt = '数值超过上限';
    4. }
    5. document.getElementById('prompt').innerHTML = txt;

    如果想禁止浏览器弹出表单验证的报错信息,可以监听invalid事件。

    1. var input = document.getElementById('username');
    2. var form = document.getElementById('form');
    3. var elem = document.createElement('div');
    4. elem.id = 'notify';
    5. elem.style.display = 'none';
    6. input.addEventListener('invalid', function (event) {
    7. event.preventDefault();
    8. if (!event.target.validity.valid) {
    9. elem.textContent = '用户名必须是小写字母';
    10. elem.className = 'error';
    11. elem.style.display = 'block';
    12. input.className = 'invalid animated shake';
    13. }
    14. });
    15. input.addEventListener('input', function(event){
    16. if ( 'block' === elem.style.display ) {
    17. elem.style.display = 'none';
    18. }
    19. });

    上面代码中,一旦发生invalid事件(表单验证失败),event.preventDefault()用来禁止浏览器弹出默认的验证失败提示,然后设置定制的报错提示框。

    表单的 novalidate 属性

    表单元素的 HTML 属性novalidate,可以关闭浏览器的自动校验。

    1. <form novalidate>
    2. </form>

    这个属性也可以在脚本里设置。

    1. form.noValidate = true;

    如果表单元素没有设置novalidate属性,那么提交按钮(<button><input>元素)的formnovalidate属性也有同样的作用。

    1. <form>
    2. <input type="submit" value="submit" formnovalidate>
    3. </form>

    enctype 属性

    表单能够用四种编码,向服务器发送数据。编码格式由表单的enctype属性决定。

    下面四种格式,都可以将这个表单发送到服务器。

    (1)GET 方法

    如果表单使用GET方法发送数据,enctype属性无效。

    1. <form
    2. action="register.php"
    3. method="get"
    4. onsubmit="AJAXSubmit(this); return false;"
    5. >
    6. </form>

    数据将以 URL 的查询字符串发出。

    1. ?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

    (2)application/x-www-form-urlencoded

    如果表单用POST方法发送数据,并省略enctype属性,那么数据以application/x-www-form-urlencoded格式发送(因为这是默认值)。

    1. <form
    2. action="register.php"
    3. method="post"
    4. onsubmit="AJAXSubmit(this); return false;"
    5. >
    6. </form>

    发送的 HTTP 请求如下。

    1. Content-Type: application/x-www-form-urlencoded
    2. foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A

    上面代码中,数据体里面的%0D%0A代表换行符(\r\n)。

    (3)text/plain

    如果表单使用POST方法发送数据,enctype属性为text/plain,那么数据将以纯文本格式发送。

    1. <form
    2. action="register.php"
    3. method="post"
    4. enctype="text/plain"
    5. onsubmit="AJAXSubmit(this); return false;"
    6. >
    7. </form>

    发送的 HTTP 请求如下。

    1. Content-Type: text/plain
    2. foo=bar
    3. baz=The first line.
    4. The second line.

    (4)multipart/form-data

    如果表单使用POST方法,enctype属性为multipart/form-data,那么数据将以混合的格式发送。

    1. <form
    2. action="register.php"
    3. method="post"
    4. enctype="multipart/form-data"
    5. onsubmit="AJAXSubmit(this); return false;"
    6. >
    7. </form>

    发送的 HTTP 请求如下。

    1. Content-Type: multipart/form-data; boundary=---------------------------314911788813839
    2. -----------------------------314911788813839
    3. Content-Disposition: form-data; name="foo"
    4. bar
    5. -----------------------------314911788813839
    6. Content-Disposition: form-data; name="baz"
    7. The first line.
    8. The second line.
    9. -----------------------------314911788813839--

    这种格式也是文件上传的格式。

    用户上传文件,也是通过表单。具体来说,就是通过文件输入框选择本地文件,提交表单的时候,浏览器就会把这个文件发送到服务器。

    1. <input type="file" id="file" name="myFile">

    此外,还需要将表单<form>元素的method属性设为POSTenctype属性设为multipart/form-data。其中,enctype属性决定了 HTTP 头信息的Content-Type字段的值,默认情况下这个字段的值是application/x-www-form-urlencoded,但是文件上传的时候要改成multipart/form-data

    1. <form method="post" enctype="multipart/form-data">
    2. <div>
    3. <label for="file">选择一个文件</label>
    4. <input type="file" id="file" name="myFile" multiple>
    5. </div>
    6. <div>
    7. <input type="submit" id="submit" name="submit_button" value="上传" />
    8. </div>
    9. </form>

    上面的 HTML 代码中,file 控件的multiple属性,指定可以一次选择多个文件;如果没有这个属性,则一次只能选择一个文件。

    1. var fileSelect = document.getElementById('file');
    2. var files = fileSelect.files;

    然后,新建一个 FormData 实例对象,模拟发送到服务器的表单数据,把选中的文件添加到这个对象上面。

    1. var formData = new FormData();
    2. for (var i = 0; i < files.length; i++) {
    3. var file = files[i];
    4. // 只上传图片文件
    5. if (!file.type.match('image.*')) {
    6. continue;
    7. }
    8. formData.append('photos[]', file, file.name);
    9. }

    最后,使用 Ajax 向服务器上传文件。

    1. var xhr = new XMLHttpRequest();
    2. xhr.open('POST', 'handler.php', true);
    3. xhr.onload = function () {
    4. if (xhr.status !== 200) {
    5. console.log('An error occurred!');
    6. }
    7. };
    8. xhr.send(formData);

    参考链接

    • , Thoriq Firdaus