表单 表单就是用来提交数据的
表单(<form>
)用来收集用户提交的数据,发送到服务器。比如,用户提交用户名和密码,让服务器验证,就要通过表单。表单提供多种控件,让开发者使用,具体的控件种类和用法请参考 HTML 语言的教程。本章主要介绍 JavaScript 与表单的交互。
< form action = "/handling-page" method = "post" >
< div >
< label for = "name" >用户名:</ label >
< input type = "text" id = "name" name = "user_name" />
</ div >
< div >
< label for = "passwd" >密码:</ label >
< input type = "password" id = "passwd" name = "user_passwd" />
</ div >
< div >
< input type = "submit" id = "submit" name = "submit_button" value = "提交" />
</ div >
</ form >
上面代码就是一个简单的表单,包含三个控件:用户名输入框、密码输入框和提交按钮。
用户点击“提交”按钮,每一个控件都会生成一个键值对,键名是控件的name
属性,键值是控件的value
属性,键名和键值之间由等号连接。比如,用户名输入框的name
属性是user_name
,value
属性是用户输入的值,假定是“张三”,提交到服务器的时候,就会生成一个键值对user_name=张三
。
所有的键值对都会提交到服务器。但是,提交的数据格式跟<form>
元素的method
属性有关。该属性指定了提交数据的 HTTP 方法。如果是 GET 方法,所有键值对会以 URL 的查询字符串形式,提交到服务器,比如/handling-page?user_name=张三&user_passwd=123&submit_button=提交
。下面就是 GET 请求的 HTTP 头信息。
GET /handling-page?user_name=张三&user_passwd=123&submit_button=提交
Host : example.com
如果是 POST 方法,所有键值对会连接成一行,作为 HTTP 请求的数据体发送到服务器,比如user_name=张三&user_passwd=123&submit_button=提交
。下面就是 POST 请求的头信息。
POST /handling-page HTTP / 1.1
Host : example.com
Content-Type : application/x-www-form-urlencoded
Content-Length : 74
user_name=张三&user_passwd=123&submit_button=提交
注意,实际提交的时候,只要键值不是 URL 的合法字符(比如汉字“张三”和“提交”),浏览器会自动对其进行编码。
点击submit
控件,就可以提交表单。
< form >
< input type = "submit" value = "提交" >
</ form >
上面表单就包含一个submit
控件,点击这个控件,浏览器就会把表单数据向服务器提交。
注意,表单里面的<button>
元素如果没有用type
属性指定类型,那么默认就是submit
控件。
< form >
< button >提交</ button >
</ form >
上面表单的<button>
元素,点击以后也会提交表单。
除了点击submit
控件提交表单,还可以用表单元素的submit()
方法,通过脚本提交表单。
formElement. submit ();
表单元素的reset()
方法可以重置所有控件的值(重置为默认值)。
formElement. reset ()
表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。但是有时候,我们希望通过脚本完成这个过程,构造或编辑表单的键值对,然后通过脚本发送给服务器。浏览器原生提供了 FormData 对象来完成这项工作。
FormData()
首先是一个构造函数,用来生成表单的实例。
var formdata = new FormData (form);
FormData()
构造函数的参数是一个 DOM 的表单元素,构造函数会自动处理表单的键值对。这个参数是可选的,如果省略该参数,就表示一个空的表单。
下面是一个表单。
< form id = "myForm" name = "myForm" >
< div >
< label for = "username" >用户名:</ label >
< input type = "text" id = "username" name = "username" >
</ div >
< div >
< label for = "useracc" >账号:</ label >
< input type = "text" id = "useracc" name = "useracc" >
</ div >
< div >
< label for = "userfile" >上传文件:</ label >
< input type = "file" id = "userfile" name = "userfile" >
</ div >
< input type = "submit" value = "Submit!" >
</ form >
我们用FormData()
处理上面这个表单。
var myForm = document. getElementById ( 'myForm' );
var formData = new FormData (myForm);
// 获取某个控件的值
formData. get ( 'username' ) // ""
// 设置某个控件的值
formData. set ( 'username' , '张三' );
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()
方法的例子。
var formData = new FormData ();
formData. set ( 'username' , '张三' );
formData. append ( 'username' , '李四' );
formData. get ( 'username' ) // "张三"
formData. getAll ( 'username' ) // ["张三", "李四"]
formData. append ( 'userpic[]' , myFileInput.files[ 0 ], 'user1.jpg' );
formData. append ( 'userpic[]' , myFileInput.files[ 1 ], 'user2.jpg' );
下面是遍历器的例子。
var formData = new FormData ();
formData. append ( 'key1' , 'value1' );
formData. append ( 'key2' , 'value2' );
for ( var key of formData. keys ()) {
console. log (key);
}
// "key1"
// "key2"
for ( var value of formData. values ()) {
console. log (value);
}
// "value1"
// "value2"
for ( var pair of formData. entries ()) {
console. log (pair[ 0 ] + ': ' + pair[ 1 ]);
}
// key1: value1
// key2: value2
// 等同于遍历 formData.entries()
for ( var pair of formData) {
console. log (pair[ 0 ] + ': ' + pair[ 1 ]);
}
// key1: value1
// key2: value2
表单提交的时候,浏览器允许开发者指定一些条件,它会自动验证各个表单控件的值是否符合条件。
<!-- 必填 -->
< input required >
<!-- 必须符合正则表达式 -->
< input pattern = "banana|cherry" >
<!-- 字符串长度必须为6个字符 -->
< input minlength = "6" maxlength = "6" >
<!-- 数值必须在1到10之间 -->
< input type = "number" min = "1" max = "10" >
<!-- 必须填入 Email 地址 -->
< input type = "email" >
<!-- 必须填入 URL -->
< input type = "URL" >
如果一个控件通过验证,它就会匹配:valid
的 CSS 伪类,浏览器会继续进行表单提交的流程。如果没有通过验证,该控件就会匹配:invalid
的 CSS 伪类,浏览器会终止表单提交,并显示一个错误信息。
input :invalid {
border-color : red ;
}
input ,
input :valid {
border-color : #ccc ;
}
除了提交表单的时候,浏览器自动校验表单,还可以手动触发表单的校验。表单元素和表单控件都有checkValidity()
方法,用于手动触发校验。
// 触发整个表单的校验
form. checkValidity ()
// 触发单个表单控件的校验
formControl. checkValidity ()
checkValidity()
方法返回一个布尔值,true
表示通过校验,false
表示没有通过校验。因此,提交表单可以封装为下面的函数。
function submitForm ( action ) {
var form = document. getElementById ( 'form' );
form.action = action;
if (form. checkValidity ()) {
form. submit ();
}
}
控件元素的willValidate
属性是一个布尔值,表示该控件是否会在提交时进行校验。
// HTML 代码如下
// <form novalidate>
// <input id="name" name="name" required />
// </form>
var input = document. querySelector ( '#name' );
input.willValidate // true
控件元素的validationMessage
属性返回一个字符串,表示控件不满足校验条件时,浏览器显示的提示文本。以下两种情况,该属性返回空字符串。
// HTML 代码如下
// <form><input type="text" required></form>
document. querySelector ( 'form input' ).validationMessage
// "请填写此字段。"
下面是另一个例子。
var myInput = document. getElementById ( 'myinput' );
if ( ! myInput. checkValidity ()) {
document. getElementById ( 'prompt' ).innerHTML = myInput.validationMessage;
}
控件元素的setCustomValidity()
方法用来定制校验失败时的报错信息。它接受一个字符串作为参数,该字符串就是定制的报错信息。如果参数为空字符串,则上次设置的报错信息被清除。
这个方法可以替换浏览器内置的表单验证报错信息,参数就是要显示的报错信息。
< form action = "somefile.php" >
< input
type = "text"
name = "username"
placeholder = "Username"
pattern = "[a-z]{1,15}"
id = "username"
>
< input type = "submit" >
</ form >
上面的表单输入框,要求只能输入小写字母,且不得超过15个字符。如果输入不符合要求(比如输入“ABC”),提交表单的时候,Chrome 浏览器会弹出报错信息“Please match the requested format.”,禁止表单提交。下面使用setCustomValidity()
方法替换掉报错信息。
var input = document. getElementById ( 'username' );
input. oninvalid = function ( event ) {
event.target. setCustomValidity (
'用户名必须是小写字母,不能为空,最长不超过15个字符'
);
}
上面代码中,setCustomValidity()
方法是在invalid
事件的监听函数里面调用。该方法也可以直接调用,这时如果参数不为空字符串,浏览器就会认为该控件没有通过校验,就会立刻显示该方法设置的报错信息。
/* HTML 代码如下
<form>
<p><input type="file" id="fs"></p>
<p><input type="submit"></p>
</form>
*/
document. getElementById ( 'fs' ).onchange = checkFileSize;
function checkFileSize () {
var fs = document. getElementById ( 'fs' );
var files = fs.files;
if (files. length > 0 ) {
if (files[ 0 ].size > 75 * 1024 ) {
fs. setCustomValidity ( '文件不能大于 75KB' );
return ;
}
}
fs. setCustomValidity ( '' );
}
上面代码一旦发现文件大于 75KB,就会设置校验失败,同时给出自定义的报错信息。然后,点击提交按钮时,就会显示报错信息。这种校验失败是不会自动消除的,所以如果所有文件都符合条件,要将报错信息设为空字符串,手动消除校验失败的状态。
控件元素的属性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
:布尔值,表示用户没有填入必填的值。
下面是一个例子。
var input = document. getElementById ( 'myinput' );
if (input.validity.valid) {
console. log ( '通过校验' );
} else {
console. log ( '校验失败' );
}
下面是另外一个例子。
var txt = '' ;
if (document. getElementById ( 'myInput' ).validity.rangeOverflow) {
txt = '数值超过上限' ;
}
document. getElementById ( 'prompt' ).innerHTML = txt;
如果想禁止浏览器弹出表单验证的报错信息,可以监听invalid
事件。
var input = document. getElementById ( 'username' );
var form = document. getElementById ( 'form' );
var elem = document. createElement ( 'div' );
elem.id = 'notify' ;
elem.style.display = 'none' ;
form. appendChild (elem);
input. addEventListener ( 'invalid' , function ( event ) {
event. preventDefault ();
if ( ! event.target.validity.valid) {
elem.textContent = '用户名必须是小写字母' ;
elem.className = 'error' ;
elem.style.display = 'block' ;
input.className = 'invalid animated shake' ;
}
});
input. addEventListener ( 'input' , function ( event ){
if ( 'block' === elem.style.display ) {
input.className = '' ;
elem.style.display = 'none' ;
}
});
上面代码中,一旦发生invalid
事件(表单验证失败),event.preventDefault()
用来禁止浏览器弹出默认的验证失败提示,然后设置定制的报错提示框。
表单元素的 HTML 属性novalidate
,可以关闭浏览器的自动校验。
< form novalidate >
</ form >
这个属性也可以在脚本里设置。
form.noValidate = true ;
如果表单元素没有设置novalidate
属性,那么提交按钮(<button>
或<input>
元素)的formnovalidate
属性也有同样的作用。
< form >
< input type = "submit" value = "submit" formnovalidate >
</ form >
表单能够用四种编码,向服务器发送数据。编码格式由表单的enctype
属性决定。
假定表单有两个字段,分别是foo
和baz
,其中foo
字段的值等于bar
,baz
字段的值是一个分为两行的字符串。
The first line.
The second line.
下面四种格式,都可以将这个表单发送到服务器。
(1)GET 方法
如果表单使用GET
方法发送数据,enctype
属性无效。
< form
action = "register.php"
method = "get"
onsubmit = " AJAXSubmit ( this ); return false ;"
>
</ form >
数据将以 URL 的查询字符串发出。
? foo= bar&baz=The%20first%20line.%0AThe%20second%20line.
(2)application/x-www-form-urlencoded
如果表单用POST
方法发送数据,并省略enctype
属性,那么数据以application/x-www-form-urlencoded
格式发送(因为这是默认值)。
< form
action = "register.php"
method = "post"
onsubmit = " AJAXSubmit ( this ); return false ;"
>
</ form >
发送的 HTTP 请求如下。
Content-Type : application/x-www-form-urlencoded
foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
上面代码中,数据体里面的%0D%0A
代表换行符(\r\n
)。
(3)text/plain
如果表单使用POST
方法发送数据,enctype
属性为text/plain
,那么数据将以纯文本格式发送。
< form
action = "register.php"
method = "post"
enctype = "text/plain"
onsubmit = " AJAXSubmit ( this ); return false ;"
>
</ form >
发送的 HTTP 请求如下。
Content-Type : text/plain
foo=bar
baz=The first line.
The second line.
(4)multipart/form-data
如果表单使用POST
方法,enctype
属性为multipart/form-data
,那么数据将以混合的格式发送。
< form
action = "register.php"
method = "post"
enctype = "multipart/form-data"
onsubmit = " AJAXSubmit ( this ); return false ;"
>
</ form >
发送的 HTTP 请求如下。
Content-Type : multipart/form-data; boundary=---------------------------314911788813839
-----------------------------314911788813839
Content-Disposition : form-data; name="foo"
bar
-----------------------------314911788813839
Content-Disposition : form-data; name="baz"
The first line.
The second line.
-----------------------------314911788813839--
这种格式也是文件上传的格式。
用户上传文件,也是通过表单。具体来说,就是通过文件输入框选择本地文件,提交表单的时候,浏览器就会把这个文件发送到服务器。
< input type = "file" id = "file" name = "myFile" >
此外,还需要将表单<form>
元素的method
属性设为POST
,enctype
属性设为multipart/form-data
。其中,enctype
属性决定了 HTTP 头信息的Content-Type
字段的值,默认情况下这个字段的值是application/x-www-form-urlencoded
,但是文件上传的时候要改成multipart/form-data
。
< form method = "post" enctype = "multipart/form-data" >
< div >
< label for = "file" >选择一个文件</ label >
< input type = "file" id = "file" name = "myFile" multiple >
</ div >
< div >
< input type = "submit" id = "submit" name = "submit_button" value = "上传" />
</ div >
</ form >
上面的 HTML 代码中,file 控件的multiple
属性,指定可以一次选择多个文件;如果没有这个属性,则一次只能选择一个文件。
var fileSelect = document. getElementById ( 'file' );
var files = fileSelect.files;
然后,新建一个 FormData 实例对象,模拟发送到服务器的表单数据,把选中的文件添加到这个对象上面。
var formData = new FormData ();
for ( var i = 0 ; i < files. length ; i ++ ) {
var file = files[i];
// 只上传图片文件
if ( ! file.type. match ( 'image.*' )) {
continue ;
}
formData. append ( 'photos[]' , file, file.name);
}
最后,使用 Ajax 向服务器上传文件。
var xhr = new XMLHttpRequest ();
xhr. open ( 'POST' , 'handler.php' , true );
xhr. onload = function () {
if (xhr.status !== 200 ) {
console. log ( 'An error occurred!' );
}
};
xhr. send (formData);
除了发送 FormData 实例,也可以直接 AJAX 发送文件。
var file = document. getElementById ( 'test-input' ).files[ 0 ];
var xhr = new XMLHttpRequest ();
xhr. open ( 'POST' , 'myserver/uploads' );
xhr. setRequestHeader ( 'Content-Type' , file.type);
xhr. send (file);