约束验证

创建 Web 表单一直是一项复杂的任务。虽然标记表单本身很容易,但检查每个字段是否具有有效且一致的值更难,告知用户问题可能会让人头疼。 HTML5 为表单引入了新的机制:它为 <input> 元素添加了新的语义类型以及约束验证,以简化在客户端检查表单内容的工作。基本且常见的约束可以在没有 JavaScript 的情况下进行检查,方法是设置新属性;更复杂的约束可以使用约束验证 API 进行测试。

有关这些概念的基本介绍,以及示例,请参阅 表单验证教程

注意:HTML 约束验证不会消除对服务器端验证的需求。即使预期收到无效表单请求的数量要少得多,但仍然可以通过多种方式发送无效请求

  • 通过浏览器开发者工具修改 HTML。
  • 通过手工制作 HTTP 请求,而不用表单。
  • 通过编程方式将内容写入表单(某些约束验证*仅对*用户输入执行,而不会在使用 JavaScript 设置表单字段的值时执行)。

因此,您应该始终在服务器端验证表单数据,与客户端的操作保持一致。

内在约束和基本约束

在 HTML 中,基本约束以两种方式声明

  • 选择对type 属性的最语义值<input> 元素,例如,选择email 类型会自动创建一个约束,检查该值是否为有效的电子邮件地址。
  • 通过设置与验证相关的属性值,允许以简单的方式描述基本约束,无需使用 JavaScript。

语义输入类型

用于type 属性的内在约束是

输入类型 约束描述 关联违规
<input type="URL"> 该值必须是一个绝对URL,如URL 实施标准中所定义。 TypeMismatch 约束违规
<input type="email"> 该值必须是语法上有效的电子邮件地址,通常格式为[email protected],但也可以是本地的,例如username@hostname TypeMismatch 约束违规

对于这两种输入类型,如果multiple 属性已设置,则可以设置多个值,以逗号分隔的列表形式。如果这些值中任何一个不满足此处描述的条件,则会触发Type mismatch 约束违规。

请注意,大多数输入类型没有内在约束,因为某些类型被禁止进行约束验证,或者具有一个将不正确值转换为正确默认值的清理算法。

除了上面描述的type 属性外,以下属性用于描述基本约束

属性 支持该属性的输入类型 可能的值 约束描述 关联违规
pattern text, search, url, tel, email, password 一个JavaScript 正则表达式(使用globalignoreCasemultiline 标志*禁用*编译) 该值必须与模式匹配。 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 接口的checkValidity()reportValidity() 方法(HTMLInputElementHTMLSelectElementHTMLButtonElementHTMLOutputElementHTMLTextAreaElement),这仅评估该元素上的约束,允许脚本获取此信息。checkValidity() 方法返回一个布尔值,指示元素的值是否通过其约束。(这通常由用户代理在确定使用哪种 CSS 伪类:valid:invalid 时完成。)相反,reportValidity() 方法将任何约束失败报告给用户。
  • 通过调用HTMLFormElement 接口上的checkValidity()reportValidity() 方法。
  • 通过提交表单本身。

调用checkValidity() 被称为*静态地*验证约束,而调用reportValidity() 或提交表单被称为*交互式地*验证约束。

注意

  • 如果novalidate 属性已设置在<form> 元素上,则不会进行约束的交互式验证。
  • HTMLFormElement 接口上调用submit() 方法不会触发约束验证。换句话说,即使该方法不满足约束,也会将表单数据发送到服务器。改为在提交按钮上调用click() 方法。
  • minlengthmaxlength 约束仅对用户提供的输入进行检查。即使显式调用checkValidity()reportValidity(),也不会在以编程方式设置值时检查它们。

使用约束验证 API 的复杂约束

使用 JavaScript 和约束 API,可以实现更复杂的约束,例如,组合多个字段的约束,或涉及复杂计算的约束。

基本上,其理念是在某些表单字段事件(如onchange)上触发 JavaScript 以计算约束是否被违反,然后使用field.setCustomValidity() 方法设置验证结果:空字符串表示约束满足,任何其他字符串都表示出现错误,该字符串是显示给用户的错误消息。

组合多个字段的约束:邮政编码验证

邮政编码格式因国家/地区而异。大多数国家/地区不仅允许使用国家/地区代码的可选前缀(例如德国的D-,法国或瑞士的F-),而且有些国家/地区的邮政编码只有固定位数;其他国家/地区,如英国,具有更复杂的结构,允许在某些特定位置使用字母。

注意:这不是一个完整的邮政编码验证库,而只是一个对关键概念的演示。

例如,我们将添加一个脚本,用于检查此简单表单的约束验证

html
<form>
  <label for="ZIP">ZIP : </label>
  <input type="text" id="ZIP" />
  <label for="Country">Country : </label>
  <select id="Country">
    <option value="ch">Switzerland</option>
    <option value="fr">France</option>
    <option value="de">Germany</option>
    <option value="nl">The Netherlands</option>
  </select>
  <input type="submit" value="Validate" />
</form>

这将显示以下表单

首先,我们编写一个函数来检查约束本身

js
function checkZIP() {
  // For each country, defines the pattern that the ZIP has to follow
  const constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Switzerland ZIPs must have exactly 4 digits: e.g. CH-1950 or 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "France ZIPs must have exactly 5 digits: e.g. F-75012 or 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "Germany ZIPs must have exactly 5 digits: e.g. D-12345 or 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "Netherland ZIPs must have exactly 4 digits, followed by 2 letters except SA, SD and SS",
    ],
  };

  // Read the country id
  const country = document.getElementById("Country").value;

  // Get the NPA field
  const ZIPField = document.getElementById("ZIP");

  // Build the constraint checker
  const constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // Check it!
  if (constraint.test(ZIPField.value)) {
    // The ZIP follows the constraint, we use the ConstraintAPI to tell it
    ZIPField.setCustomValidity("");
  } else {
    // The ZIP doesn't follow the constraint, we use the ConstraintAPI to
    // give a message about the format required for this country
    ZIPField.setCustomValidity(constraints[country][1]);
  }
}

然后,我们将它链接到<select>onchange 事件和<input>oninput 事件

js
window.onload = () => {
  document.getElementById("Country").onchange = checkZIP;
  document.getElementById("ZIP").oninput = checkZIP;
};

限制文件上传之前的大小

另一个常见的约束是限制要上传的文件的大小。在将文件传输到服务器之前在客户端检查这一点,需要组合约束验证 API,尤其是field.setCustomValidity() 方法,以及另一个 JavaScript API,此处为文件 API。

以下是 HTML 部分

html
<label for="FS">Select a file smaller than 75 kB : </label>
<input type="file" id="FS" />

这将显示

JavaScript 读取选定的文件,使用File.size() 方法获取其大小,将其与(硬编码的)限制进行比较,并调用约束 API 以告知浏览器是否存在违规

js
function checkFileSize() {
  const FS = document.getElementById("FS");
  const files = FS.files;

  // If there is (at least) one file selected
  if (files.length > 0) {
    if (files[0].size > 75 * 1024) {
      // Check the constraint
      FS.setCustomValidity("The selected file must not be larger than 75 kB");
      FS.reportValidity();
      return;
    }
  }
  // No custom constraint violation
  FS.setCustomValidity("");
}

最后,我们将该方法与正确的事件挂钩

js
window.onload = () => {
  document.getElementById("FS").onchange = checkFileSize;
};

约束验证的视觉样式

除了设置约束之外,Web 开发人员还希望控制向用户显示的消息以及消息的样式。

控制元素的外观

可以使用 CSS 伪类控制元素的外观。

:required 和 :optional CSS 伪类

:required:optional 伪类 允许编写匹配具有required 属性或不具有该属性的表单元素的选择器。

:placeholder-shown CSS 伪类

参见:placeholder-shown

:valid :invalid CSS 伪类

:valid:invalid 伪类 用于表示<input> 元素,根据输入的类型设置,其内容分别验证和无法验证。这些类允许用户对有效或无效的表单元素进行样式设置,以便更轻松地识别格式正确或错误的元素。

控制约束违规的文本

以下项目可以帮助控制约束违规的文本

  • 以下元素上的setCustomValidity(message) 方法
    • <fieldset>。注意:在 fieldset 元素上设置自定义有效性消息不会阻止大多数浏览器中的表单提交。
    • <input>
    • <output>
    • <select>
    • 提交按钮(使用具有submit 类型的<button> 元素或具有submit 类型的input 元素创建。其他类型的按钮不参与约束验证)。
    • <textarea>
  • ValidityState 接口描述了上面列出的元素类型的validity 属性返回的对象。它表示输入值无效的各种方式。它们共同帮助解释了为什么元素的值无效(如果它无效)。