1. 1. 使用浏览器内置的表单校验
    1. 1.1. 元素校验不通过时
    2. 1.2. input元素的校验约束
      1. 1.2.1. 举个例子
      2. 1.2.2. required特性
    3. 1.3. 其他校验约束
    4. 1.4. 定制错误信息
  2. 2. 使用Javascript校验表单
    1. 2.1. HTML5约束验证API
      1. 2.1.1. 约束验证API的属性
      2. 2.1.2. 约束验证API的方法
      3. 2.1.3. 使用约束验证API的例子
    2. 2.2. 不使用内置API来校验表单
      1. 2.2.1. 未使用约束验证API的例子
      2. 2.2.2. 远端校验
  3. 3. 结论

当你需要经常在服务器上校验数据时,在Web页面上的另加校验就有诸多好处。多数情况下,用户会被表单惹恼。而当用户填完表单时就校验数据,既有助于用户立即发现他们犯的错误,也能减少等待HTTP响应的时间、并减少服务器对错误表单输入的处理。本文介绍的,就是如何在表单所在页面对表单数据进行校验。

使用浏览器内置的表单校验

HTML5的一大特性,就是在不依赖脚本的情况下校验大多数的用户数据。该特性是通过使用表单元素上的校验特性来实现的。

元素校验不通过时

当一个元素校验不通过时,会发生两件事:

  • 该元素会匹配到:invalid这个CSS伪类,它能让你给校验不过的元素提供特定的样式。类似地,校验通过的元素则会匹配:valid伪类。
  • 当用户要发送数据时,浏览器会阻止这个表单、并展示一段错误信息。

input元素的校验约束

所有的<input>元素都能使用pattern特性来作校验。该特性采用一个区分大小写的正则表达式作为它的值。若元素的值非空、或者不匹配该特性所指定的正则表达式,该元素就会被认为是校验不通过的。

举个例子

1
2
3
4
5
<form>
<label for="choose">Would you prefer a banana or a cherry?</label>
<input id="choose" name="i_like" pattern="banana|cherry">
<button>Submit</button>
</form>
1
2
3
4
5
6
7
input:invalid {
border: 1px solid red;
}
input:valid {
border: 1px solid green;
}

在本例中,<input>元素可以接受如下三种值:空字符串、字符串”banana”或”cherry”。

required特性

若某个元素需要在提交表单之前填一个值,那我们可以通过required特性来标记该元素。当该特性为true时,所在的文本域不允许为空。

1
2
3
4
5
<form>
<label for="choose">Would you prefer a banana or cherry?</label>
<input id="choose" name="i_like" pattern="banana|cherry" required>
<button>Submit</button>
</form>
1
2
3
4
5
6
7
input:invalid {
border: 1px solid red;
}
input:valid {
border: 1px solid green;
}

注意文本框相比上个例子有何不同:

注意:<input>元素的type特性被设为emailurl时,无需再使用pattern特性来坐校验。指定email类型时,文本框的值就得是个正确格式的email地址(同时指定mutiple特性时,还可以是一个逗号分隔的email地址列表)。指定url类型的文本框则会自动匹配一个URL。

其他校验约束

所有接受用户输入的表单元素(<textarea>, <select>等)都支持required特性,但要注意<textarea>元素并不支持pattern特性。

所有的文本输入框(<input><textarea>)都可以使用maxlength特性来约束文本大小。输入框的值若大于maxlength指定的值,就会校验不通过。但是浏览器通常不会让用户输入超过文本框指定长度的文本。

对于数字输入框,minmax特性也提供了校验约束。若输入框的值小于min的值或大于max的值,输入框就会校验不通过。

来个完整点的例子:

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
<form>
<p>
<fieldset>
<legend>Title<abbr title="This field is mandatory">*</abbr></legend>
<input type="radio" required name="title" id="r1" value="Mr" ><label for="r1">Mr. </label>
<input type="radio" required name="title" id="r2" value="Ms"><label for="r2">Ms.</label>
</fieldset>
</p>
<p>
<label for="n1">How old are you?</label>
<!-- pattern特性可以给没实现number类型input元素但支持pattern特性的浏览器一个向后兼容。
请注意支持pattern特性的浏览器会使用数字输入框时让该特性失效。
该特性在这里的用法仅是做个向后兼容而已。 -->
<input type="number" min="12" max="120" step="1" id="n1" name="age"
pattern="\d+">
</p>
<p>
<label for="t1">What's your favorite fruit?<abbr title="This field is mandatory">*</abbr></label>
<input type="text" id="t1" name="fruit" list="l1" required
pattern="[Bb]anana|[Cc]herry|[Aa]pple|[Ss]trawberry|[Ll]emon|[Oo]range">
<datalist id="l1">
<option>Banana</option>
<option>Cherry</option>
<option>Apple</option>
<option>Strawberry</option>
<option>Lemon</option>
<option>Orange</option>
</datalist>
</p>
<p>
<label for="t2">What's your e-mail?</label>
<input type="email" id="t2" name="email">
</p>
<p>
<label for="t3">Leave a short message</label>
<textarea id="t3" name="msg" maxlength="140" rows="5"></textarea>
</p>
<p>
<button>Submit</button>
</p>
</form>
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
body {
font: 1em sans-serif;
padding: 0;
margin : 0;
}
form {
max-width: 200px;
margin: 0;
padding: 0 5px;
}
p > label {
display: block;
}
input[type=text],
input[type=email],
input[type=number],
textarea,
fieldset {
/* 需要给Webkit浏览器下的表单元素适当的样式 */
-webkit-appearance: none;
width : 100%;
border: 1px solid #333;
margin: 0;
font-family: inherit;
font-size: 90%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input:invalid {
box-shadow: 0 0 5px 1px red;
}
input:focus:invalid {
outline: none;
}

定制错误信息

如我们上面看到的几个例子所示,每次用户要发送未通过校验的表单时,浏览器都会展示一段错误信息。该信息的展示方式取决于浏览器本身。

这些自动生成的信息有两大缺点:

  • 没有标准的方法来用CSS改变其外观和体验。
  • 它们还要依赖浏览器本地信息,这意味着你会在使用一种语言的页面中,遇到错误信息使用另一种语言展示的情况。

使用法语版本的浏览器浏览英文页面

浏览器 渲染效果
Firefox 17 (Windows 7)
Chrome 22 (Windows 7)
Opera 12.10 (Mac OSX)

要定制这些信息的外观和文本,你必须使用JavaScript,没有只使用HTML和CSS的方法。

HTML5提供了约束验证API来检查和定制表单元素的状态。此外它也能改变错误信息的文本。来看个例子:

1
2
3
4
5
<form>
<label for="mail">I would like you to provide me an e-mail</label>
<input type="email" id="mail" name="mail">
<button>Submit</button>
</form>

在Javascript中,你可以调用setCustomValidity())方法:

1
2
3
4
5
6
7
8
9
var email = document.getElementById("mail");
email.addEventListener("keyup", function (event) {
if (email.validity.typeMismatch) {
email.setCustomValidity("I expect an e-mail, darling!");
} else {
email.setCustomValidity("");
}
});

使用Javascript校验表单

若你想要控制错误信息的样式和效果,或者想处理那些不支持HTML5表单验证的浏览器,那你就只能使用Javascript了。

HTML5约束验证API

现在越来越多的浏览器开始支持约束验证API,而且也支持得越来越靠谱。该API囊括了各个表单元素上的一系列方法和属性。

约束验证API的属性

属性 描述
validationMessage 可以是一段本地化的信息,用于描述表单控件所不满足的验证条件(如果有的话);当控件不支持任何约束验证(willValidae的值为false)、或控件的值满足了约束条件时,也可以是一个空字符串
validity 一个用于描述元素校验阶段的validityState对象
validity.customError 元素触发了定制的错误时返回true,否则返回false
validity.patternMismatch 元素的值不匹配提供的模板时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid伪类
validity.rangeOverflow 元素的值大于提供的最大值时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid:out-fo-range伪类
validity.rangeUnderflow 元素的值小于提供的最小值时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid:out-fo-range伪类
validity.stepMismatch 元素的值不符合step特性提供的规则时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid:out-fo-range伪类
validity.tooLong 元素的值的长度超过要求的最大长度时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid:out-fo-range伪类
validity.typeMismatch 元素的值不是正确的语法时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid伪类
validity.valid 元素的值没有校验出的问题时返回true,否则返回false。当返回true时,该元素会匹配CSS的:valid,否则会匹配:invalid伪类
validity.valueMissing 元素是必填项但没有值时返回true,否则返回false。当返回true时,该元素会匹配CSS的:invalid伪类
willValidate 元素在表单提交时会被校验则返回true,否则返回false

约束验证API的方法

方法 描述
checkValidity() 元素的值没有校验出的问题时返回true,否则返回false。当校验不通过时,该方法还会在相应元素上触发invalid事件。
setCustomValidity(message) 给元素添加一段定制的错误信息;若你手动设置了错误信息,则给元素会被当做校验未通过,并显示出特定的错误。这就能让你摆脱标准的约束验证API提供的错误信息,直接使用Javascript来建立自己的错误信息。该信息在报错时会展示给用户。

对于老旧浏览器,我们可以使用H5F之类的polyfill来弥补其对于约束验证API的不支持。由于这里你已经使用了Javascript,所以使用polyfill也并不会成为你设计或实现网站、Web应用时的额外负担。

使用约束验证API的例子

来看下如何使用该API来建立定制的错误信息,首先HTLM如下:

1
2
3
4
5
6
7
8
9
10
<form novalidate>
<p>
<label for="mail">
<span>Please enter an email address:</span>
<input type="email" id="mail" name="mail">
<span class="error" aria-live="polite"></span>
</label>
<p>
<button>Submit</button>
</form>

这个简单的表格用了novalidate特性来关闭浏览器的自动校验;这样我们的脚本就能控制整个校验过程了。但是这样做却不会禁用掉约束验证API、以及 :valid, :invalid, :in-range, :out-of-range 等一系列CSS伪类。这就意味着,虽然浏览器不会自动在表单发送前校验它的数据,但你仍可自己来做校验、并且按需给予表单样式。

aria-live特性用来确保我们定制的错误信息能展示给任何人,包括那些使用诸如屏幕阅读器等无障碍设备的人。

CSS
这段CSS用于装饰我们的表单、并让错误的输出看起来更吸引人些。

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
/* 这段只是用来美化表单 */
body {
font: 1em sans-serif;
padding: 0;
margin : 0;
}
form {
max-width: 200px;
}
p * {
display: block;
}
input[type=email]{
-webkit-appearance: none;
width: 100%;
border: 1px solid #333;
margin: 0;
font-family: inherit;
font-size: 90%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* 这段是给校验未通过的输入框的样式 */
input:invalid{
border-color: #900;
background-color: #FDD;
}
input:focus:invalid {
outline: none;
}
/* 这段是给错误信息的样式 */
.error {
width : 100%;
padding: 0;
font-size: 80%;
color: white;
background-color: #900;
border-radius: 0 0 5px 5px;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.error.active {
padding: 0.3em;
}

JavaScript
下面的Javascript代码用来处理我们自定义的错误校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 获取DOM节点的方法有许多,这里我们获取了表单自身以及email输入框,还有一个让我们放置错误信息的span元素。
var form = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');
var error = document.querySelector('.error');
email.addEventListener("keyup", function (event) {
// 每次用户输入时,我们都会检查email输入框是否合法
if (email.validity.valid) {
// 当校验通过时,如果已经有一段错误信息在显示了,就移除掉错误信息
error.innerHTML = ""; // 重置消息内容
error.className = "error"; // 重置消息的显示状态
}
}, false);
form.addEventListener("submit", function (event) {
// 每次用户要提交表单时,我们都会检查email输入框是否合法
if (!email.validity.valid) {
// 如果输入框不合法,我们就展示那段定制的错误信息
error.innerHTML = "I expect an e-mail, darling!";
error.className = "error active";
// 通过取消事件来阻止表单提交
event.preventDefault();
}
}, false);

下面是运行结果:

约束验证API给了我们处理表单校验的强大工具,并让我们能有力地控制其用户界面,这就远超过只用HTML和CSS所能做的事了。

不使用内置API来校验表单

有时,面对老旧浏览器和定制的组件,你不能(或者不想)使用约束验证API。但在这种情况下,你仍然可以使用Javascript来校验你的表单。表单的校验更多是个用户界面的问题、而非真正的数据校验。

要校验一个表单,你得先问你自己几个问题:

我需要进行什么类型的校验?
你得先决定如何校验数据,这取决于你:可以用字符串操作、类型转换、正则表达式等等。只要记住表单数据通常是一段文本,而且传到你的脚本里时也是一些字符串就好。

表单校验未通过时,我应该做什么?
这就是个UI的问题了。得靠你决定表单的行为:是不论是否通过都发送数据呢?还是高亮那些出现错误的字段?还是展示一些错误信息?

如何帮助用户用户纠正非法数据?
要减少用户的不满,很重要的是提供尽可能多的帮助信息,以指导用户纠正他们的输入。还应该提供些前置的输入建议、和清晰的错误信息,让用户知道该输入什么。

若你还想深入了解表单校验的UI要求,可以读读下面这些有用的文章:

未使用约束验证API的例子

为了更好地说明,我们接下来要重构上述的例子,让它能在老旧浏览器上运行:

1
2
3
4
5
6
7
8
9
10
11
<form>
<p>
<label for="mail">
<span>Please enter an email address:</span>
<input type="text" class="mail" id="mail" name="mail">
<span class="error" aria-live="polite"></span>
</label>
<p>
<!-- 某些老旧浏览器上button元素的`type`特性不会被默认指定为`submit`-->
<button type="submit">Submit</button>
</form>

如你所见,HTML的和之前几乎一模一样;这里只移除了HTML5的部分。要注意的是ARIA是个并不依赖于HTML5的独立特性,所以我们仍会保留它。

CSS
类似地,CSS不需要做大规模修改;只要把:invalid伪类转为真的类,并且不去使用IE6不支持的特性选择器即可。

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
/* 这段只是用来美化表单 */
body {
font: 1em sans-serif;
padding: 0;
margin : 0;
}
form {
max-width: 200px;
}
p * {
display: block;
}
input.mail {
-webkit-appearance: none;
width: 100%;
border: 1px solid #333;
margin: 0;
font-family: inherit;
font-size: 90%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* 这段是给校验未通过的输入框的样式 */
input.invalid{
border-color: #900;
background-color: #FDD;
}
input:focus.invalid {
outline: none;
}
/* 这段是给错误信息的样式 */
.error {
width : 100%;
padding: 0;
font-size: 80%;
color: white;
background-color: #900;
border-radius: 0 0 5px 5px;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.error.active {
padding: 0.3em;
}

JavaScript
Javascript代码的改变最大,为了兼容我们需要做更多的事情。

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
// 老旧浏览器上获取DOM节点的方法少了些
var form = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');
// 下面是个访问DOM里下个兄弟元素节点的的技巧
// 但这样做很危险,因为很容易就会建立一个死循环
// 在现代浏览器上,应该使用element.nextElementSibling
var error = email;
while ((error = error.nextSibling).nodeType != 1);
// 按照HTML5的规范来
var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
// 许多老旧浏览器不支持addEventListener方法
// 下面是个解决该问题的简单方法,当然方法远不止这一种
function addEvent(element, event, callback) {
var previousEventCallBack = element["on"+event];
element["on"+event] = function (e) {
var output = callback(e);
// 回调返回`false`时会终止回调链并结束事件回调的执行
if (output === false) return false;
if (typeof previousEventCallBack === 'function') {
output = previousEventCallBack(e);
if(output === false) return false;
}
}
};
// 现在可以来重构我们的校验了
// 由于我们不依赖CSS伪类,故而必须在email字段明确设置valid/invalid类
addEvent(window, "load", function () {
// 这里我们检验该字段是否为空(注意,该字段不是必填的)
// 若不是,我们再检验它的内容是否为正确格式的email地址
var test = email.value.length === 0 || emailRegExp.test(email.value);
email.className = test ? "valid" : "invalid";
});
// 定义用户在该字段输入时会发生什么
addEvent(email, "keyup", function () {
var test = email.value.length === 0 || emailRegExp.test(email.value);
if (test) {
email.className = "valid";
error.innerHTML = "";
error.className = "error";
} else {
email.className = "invalid";
}
});
// 定义用户提交数据时会发生什么
addEvent(form, "submit", function () {
var test = email.value.length === 0 || emailRegExp.test(email.value);
if (!test) {
email.className = "invalid";
error.innerHTML = "I expect an e-mail, darling!";
error.className = "error active";
// 某些老旧浏览器不支持event.preventDefault()方法
return false;
} else {
email.className = "valid";
error.innerHTML = "";
error.className = "error";
}
});

结果如下所示:

如你所见,自己建立一个校验系统并不是一件难事。难点就在于如何跨平台、并在你创建的任何表单上都能使用它。现在有许多库能执行表单校验,你应该毫不犹豫地使用他们。如下是几个例子:

远端校验

有些情况下使用远端校验还是挺有用的。在用户输入的数据得添加到应用服务器的数据存储时,这种校验是有必要的。一个相应的例子就是需要填用户名的注册表单。为避免用户名重复,更聪明的办法是发送一个AJAX请求来检查用户名的可用性,而不是让用户发送完数据、然后再返回一个带有错误的表单。

使用这种校验,需要注意以下几点:

  • 因为需要公开暴露API和一些数据,所以得保证数据不是敏感的。
  • 由于网络存在延迟,故得进行异步校验。这就得靠一些UI设计来保证校验不能正确执行时,用户操作不至于被阻塞。

结论

表单校验并不用靠复杂的Javascript,但它要求我们认真为用户着想,始终得帮用户纠正他们输入的数据。要做到这点,请确保:

  • 展示明确的错误信息
  • 约定好输入格式
  • 指出错误出现的确切位置(特别是在那些巨大的表单中)