约束验证
创建 web 表单始终是一个复杂的任务。仅仅组装表单是容易的,但是检查每一个字段的值是否有效并且一致是一件更加困难的事情,而如何告知用户错误所在可能会令人头痛。HTML5 引入了表单相关的一些新机制:它为 <input>
元素和约束验证增加了一些新的语义类型,使得客户端检查表单内容变得容易。基本上,通过设置一些新的属性,常用的约束条件可以无需 JavaScript 代码而检测到;对于更复杂的约束条件的校验可以尝试使用约束验证 API。
对于这些概念的基本介绍和示例,参阅表单数据校验教程一页的介绍。
备注:HTML 约束验证并不能移除服务端验证的需要。尽管无效的表单请求要少得多,但无效的表单请求仍然可能被发送,例如被试图欺骗你的网络应用的坏人发送。因此,你需要始终在服务端验证输入约束,其方式与在客户端所做的一致。
固有和基本的约束
语义的 input 类型
type
属性中的固有约束有:
Input 类型 | 约束描述 | 相关违约 |
---|---|---|
<input type="URL"> |
值必须为 URL 现行标准定义的绝对 URL 地址。 | TypeMismatch 约束违反 |
<input type="email"> |
该值必须是一个语法上有效的电子邮件地址,其格式一般为 username@hostname.tld ,但也可以是本地的,如 username@hostname 。 |
TypeMismatch 约束违反 |
对于这两种输入类型,如果设置了 multiple
属性,可以设置几个值,作为一个逗号分隔的列表。如果其中任何一个不满足这里描述的条件,就会触发 TypeMismatch 约束的违反。
请注意,大多数 input 类型没有内在的约束,因为有些类型被禁止在约束验证中使用,或者有一个净化算法将不正确的值转化为正确的默认值。
验证相关的属性
除了上面描述的 type
属性外,以下属性用于描述基本约束。
属性 | 支持该特性的 Input 类型 | 可接受的值 | 约束描述 | 相关违约 |
---|---|---|---|---|
pattern |
text 、search 、url 、tel 、email 、password |
一个JavaScript 正则表达式(以 global 、ignoreCase 标志编译,且禁用了 multiline 标志) |
输入的值必须符合模式。 |
patternMismatch
约束违反
|
min |
range 、number |
一个有效的数字 | 输入的值必须大于等于该属性值。 |
rangeUnderflow
约束违反
|
date 、month 、week |
一个有效的日期值 | |||
datetime-local 、time |
一个有效的日期时间值 | |||
max |
range 、number |
一个有效的数字 | 输入的值必须小于等于该属性值。 |
rangeOverflow
约束违反
|
date 、month 、week |
一个有效的日期值 | |||
datetime-local 、time |
一个有效的日期时间值 | |||
required |
text 、search 、url 、tel 、email 、password 、date 、datetime-local 、month 、week 、time 、number 、checkbox 、radio 、file ;也在 <select> 和 <textarea> 元素上可用 |
由于是一个布尔属性,所以为 none:如果存在这个属性,则为 true;否则为 false | 如果指定了这个属性,则必须输入一个值。 |
valueMissing
约束违反
|
step |
date |
一个代表天数的整数 | 如果 step 没有设置为字面量 any ,则输入值必须为 min + step 值的整数倍。 |
stepMismatch
约束违反
|
month |
一个代表月数的整数 | |||
week |
一个代表周数的整数 | |||
datetime-local 、time |
一个代表秒数的整数 | |||
range 、number |
一个整数 | |||
minlength |
text 、search 、url 、tel 、email 、password ;也在 <textarea> 元素上可用 |
一个整数长度 | 如果输入值非空,则其字符数(码点)不得少于该属性的值。对于 <textarea> ,所有换行符都被规范化为一个字符(相对于 CRLF 对)。 |
tooShort
约束违反
|
maxlength |
text 、search 、url 、tel 、email 、password ;也在 <textarea> 元素上可用 |
一个整数长度 | 字符数(码点)不得超过该属性的值。 |
tooLong
约束违反
|
约束验证过程
约束验证是通过约束验证 API 在单个表单元素上或在表单层面上,通过 <form>
元素本身完成。约束验证是通过以下方式完成的:
- 通过调用表单相关的 DOM 接口(
HTMLInputElement
、HTMLSelectElement
、HTMLButtonElement
、HTMLOutputElement
或HTMLTextAreaElement
)的checkValidity()
或reportValidity()
方法,只对这个元素进行约束评估,允许脚本获得这些信息。checkValidity()
方法返回一个布尔值,表示该元素的值是否通过其约束(这通常是由用户代理在确定哪个 CSS 伪类,:valid
或:invalid
适用时完成的)。相反,reportValidity()
方法会向用户报告任何约束失败的情况。 - 通过调用
HTMLFormElement
接口上的checkValidity()
或reportValidity()
方法。 - 通过提交表单本身。
调用 checkValidity()
也被称为约束的静态验证,调用 reportValidity()
也被称为约束的交互认证。
备注:
- 如果
<form>
元素上设置了novalidate
属性,则不发生约束验证交互。 - 在
HTMLFormElement
接口上调用submit()
方法并不触发约束条件验证。换句话说,即使表单数据不满足约束条件,该方法也会将其发送到服务器。在提交按钮上调用click()
方法来代替。
使用约束验证 API 进行复杂的约束
使用 JavaScript 和约束验证 API,可以实现更复杂的约束,例如,结合几个字段的约束,或涉及复杂计算的约束。
基本上,这个想法是在某个表单字段事件(比如 onchange)上触发 JavaScript,以计算是否违反约束,然后使用 field.setCustomValidity()
方法来设置验证的结果:一个空字符串意味着满足约束条件,任何其他字符串意味着有一个错误,这个字符串是显示给用户的错误信息。
包含多个字段的约束:邮政编码验证
每个国家的邮政编码都不相同。大多数国家允许有一个可选的国家代码前缀(如德国的 D-
,法国或瑞士的 F-
),还有其他一些国家的邮政编码只有固定的数字;其他国家,如英国,有更复杂的结构,允许在一些特定的位置有字母。
备注:这不是一个全面的邮政编码验证库,而是关键概念的演示。
作为示例,我们会向以下这个简单的表单中添加一段代码来进行约束验证:
<form>
<label for="ZIP">ZIP 码:</label>
<input type="text" id="ZIP" />
<label for="Country">国家:</label>
<select id="Country">
<option value="ch">瑞士</option>
<option value="fr">法国</option>
<option value="de">德国</option>
<option value="nl">荷兰</option>
</select>
<input type="submit" value="验证" />
</form>
以上代码显示了像这样的表单:
首先,我们来写一个函数来检查本身包含的约束:
function checkZIP() {
// 为每个国家定义 ZIP 码需要满足的模式
const constraints = {
ch: [
"^(CH-)?\\d{4}$",
"瑞士的 ZIP 码必须恰好有 4 位数字,如 CH-1950 或 1950",
],
fr: [
"^(F-)?\\d{5}$",
"法国的 ZIP 码必须恰好有 5 位数字,如 F-75012 或 75012",
],
de: [
"^(D-)?\\d{5}$",
"德国的 ZIP 码必须恰好有 5 位数字,如 D-12345 或 12345",
],
nl: [
"^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
"荷兰的 ZIP 码必须恰好有 4 位数字,后跟除 SA、SD 和 SS 的 2 位字母",
],
};
// 读取国家 ID
const country = document.getElementById("Country").value;
// 获取 NPA 字段内容
const ZIPField = document.getElementById("ZIP");
// 构建约束检查器
const constraint = new RegExp(constraints[country][0], "");
console.log(constraint);
// 检查它!
if (constraint.test(ZIPField.value)) {
// ZIP 码满足约束条件,我们使用 Constraint API 告知用户
ZIPField.setCustomValidity("");
} else {
// ZIP 不满足约束条件,我们使用 Constraint API 告知该国家所需的 ZIP 码格式
ZIPField.setCustomValidity(constraints[country][1]);
}
}
然后我们把它链接到 <select>
的 onchange 事件和 <input>
的 oninput 事件。
window.onload = () => {
document.getElementById("Country").onchange = checkZIP;
document.getElementById("ZIP").oninput = checkZIP;
};
限制所上传文件的大小
另一个常见的约束是限制要上传的文件的大小。在文件传输到服务器之前,在客户端检查这个问题需要将约束条件验证 API,特别是 field.setCustomValidity()
方法,与另一个 JavaScript API 结合起来,这里是文件 API。
这里是 HTML 部分:
<label for="FS">选择一个小于 75 kB 的文件:</label>
<input type="file" id="FS" />
会显示:
JavaScript 代码会读取所选的文件,使用 File.size()
方法来获取其大小,将其与(硬编码的)限制进行比较,如果有违反,则调用约束 API 来通知浏览器。
function checkFileSize() {
const FS = document.getElementById("FS");
const files = FS.files;
// 如果选择了(至少)一个文件
if (files.length > 0) {
if (files[0].size > 75 * 1024) {
// 检查约束条件
FS.setCustomValidity("选择的文件不能超过 75 kB");
return;
}
}
// 没有违反自定义约束条件
FS.setCustomValidity("");
}
最终,我们将这个方法链接到一个正确的事件上:
window.onload = () => {
document.getElementById("FS").onchange = checkFileSize;
};
约束验证的可视化样式
除了设置约束条件外,web 开发者还想控制向用户显示什么信息以及它们的样式。
控制元素的外观
控制约束验证的文字
以下一些方法可以控制违反约束条件的文本:
-
以下元素上的
setCustomValidity(message)
方法:<fieldset>
。备注:大多数浏览器中,在 fieldset 元素上设定自定义验证信息不会阻止表单提交。<input>
<output>
<select>
- 提交按钮(使用类型为
submit
的<button>
元素,或类型为 submit 的input
元素创建。其他类型的按钮不参与约束验证。 <textarea>
-
ValidityState
接口描述了由上述元素类型的validity
属性返回的对象。它表示一个输入值可能无效的各种方式。它们共同解释了为什么一个元素的值是无效的,则不能被验证。