Client-side form validation

 

在将数据提交到服务器之前,重要的是确保以正确的格式填写所有必需的表单控件. 这称为客户端表单验证 ,可帮助确保所提交的数据符合各种表单控件中规定的要求. 本文将引导您通过基本概念和客户端表单验证示例.

Prerequisites: 计算机知识,对HTMLCSSJavaScript有一定的了解.
Objective: 要了解什么是客户端表单验证,为什么重要,以及如何应用各种技术来实现它.

客户端验证是初步检查,是良好用户体验的重要特征; 通过在客户端捕获无效数据,用户可以立即对其进行修复. 如果到达服务器然后被拒绝,则往返服务器的往返时间将导致明显的延迟,然后再返回客户端以告知用户修复其数据.

但是, 不应将客户端验证视为详尽的安全措施! 您的应用应始终对服务器端 客户端上任何表单提交的数据执行安全检查,因为客户端验证太容易关闭,因此恶意用户仍然可以轻松地将不良数据发送到您的服务器. 阅读网站安全性以了解可能发生的情况; 实现服务器端验证在某种程度上超出了本模块的范围,但是您应该牢记这一点.

What is form validation?

转到带有注册表格的任何受欢迎的网站,您会发现,当您不以期望的格式输入数据时,它们会提供反馈. 您会收到以下消息:

  • "此字段为必填项"(您不能将此字段留空).
  • "请以xxx-xxxx格式输入您的电话号码"(必须使用特定的数据格式才能被视为有效).
  • "请输入有效的电子邮件地址"(您输入的数据格式不正确).
  • "您的密码长度必须介于8到30个字符之间,并包含一个大写字母,一个符号和一个数字." (您的数据需要非常特定的数据格式).

这称为表单验证 . 当您输入数据时,浏览器和/或Web服务器将检查以确保数据格式正确且在应用程序设置的限制内. 在浏览器中完成的验证称为客户端验证,而在服务器上完成的验证称为服务器端验证. 在本章中,我们着重于客户端验证.

如果信息格式正确,则应用程序允许将数据提交到服务器并(通常)保存在数据库中; 如果信息的格式不正确,则会向用户显示一条错误消息,说明需要更正的内容,然后让他们重试.

我们希望尽可能轻松地填写Web表单. 那么为什么我们要坚持验证我们的表格呢? 主要有以下三个原因:

  • 我们希望以正确的格式获取正确的数据. 如果我们的用户数据以错误的格式存储,不正确或被完全省略,则我们的应用程序将无法正常运行.
  • 我们要保护用户的数据 . 强制我们的用户输入安全密码可以更轻松地保护他们的帐户信息.
  • 我们要保护自己 . 恶意用户可以通过多种方式滥用不受保护的表单来破坏应用程序(请参阅网站安全性 ).

    警告:永远不要信任从客户端传递到服务器的数据. 即使您的表单可以正确验证并防止客户端输入错误,恶意用户仍然可以更改网络请求.

Different types of client-side validation

您将在网上遇到两种不同类型的客户端验证:

  • 内置的表单验证使用HTML5表单验证功能,我们已在本模块的许多地方进行了讨论. 此验证通常不需要太多JavaScript. 内置的表单验证比JavaScript具有更好的性能,但是不像JavaScript验证那样​​可自定义.
  • JavaScript验证使用JavaScript进行编码. 该验证是完全可定制的,但是您需要全部创建(或使用一个库).

Using built-in form validation

HTML5表单控件的最重要功能之一就是无需依赖JavaScript即可验证大多数用户数据的能力. 这是通过在表单元素上使用验证属性来完成的. 在课程的早期,我们已经看到了许多这样的内容,但请回顾一下:

  • required :指定在提交表单之前是否需要填写表单字段.
  • minlengthmaxlength :指定文本数据(字符串)的最小和最大长度
  • minmax :指定数字输入类型的最小值和最大值
  • type :指定数据是否需要为数字,电子邮件地址或其他特定的预设类型.
  • pattern :指定正则表达式 ,该正则表达式定义输入的数据需要遵循的模式.

如果在表单字段中输入的数据遵循上述属性指定的所有规则,则将其视为有效. 如果不是,则认为它无效.

当一个元素有效时,以下情况成立:

  • The element matches the :valid CSS pseudo-class, which lets you apply a specific style to valid elements.
  • 如果用户试图发送数据,则浏览器将提交表单,前提是没有其他阻止表单发送的表单(例如JavaScript).

当元素无效时,以下情况成立:

  • 元素匹配:invalid CSS伪类,有时还匹配其他UI伪类(例如:out-of-range ),具体取决于错误,这使您可以将特定样式应用于无效元素.
  • 如果用户尝试发送数据,浏览器将阻止该表单并显示错误消息.

注意 :有一些错误会阻止表单提交,包括badInputpatternMismatchrangeOverflowrangeUnderflowstepMismatchtooLongtooShorttypeMismatchvalueMissingcustomError .

Built-in form validation examples

在本节中,我们将测试上面讨论的一些属性.

Simple start file

让我们从一个简单的示例开始:一个输入,您可以选择喜欢香蕉还是樱桃. 此示例涉及带有关联的<label>和提交<button>的简单文本<input> <button> . 在GitHub上的fruit-start.html上找到源代码,并在下面提供一个实时示例.

<form>
  <label for="choose">Would you prefer a banana or cherry?</label>
  <input id="choose" name="i_like">
  <button>Submit</button>
</form>
input:invalid {
  border: 2px dashed red;
}

input:valid {
  border: 2px solid black;
}

首先,在硬盘驱动器上的新目录中复制fruit-start.html .

The required attribute

最简单的HTML5验证功能是required属性. 要强制输入,请将此属性添加到元素. 设置此属性后,该元素与:required UI伪类匹配,并且表单不会提交,并且在输入为空时在提交时显示错误消息. 如果为空,则与:invalid UI伪类匹配的输入也将被视为无效.

required属性添加到您的输入,如下所示.

<form>
  <label for="choose">Would you prefer a banana or cherry? (required)</label>
  <input id="choose" name="i_like" required>
  <button>Submit</button>
</form>

请注意示例文件中包含的CSS:

input:invalid {
  border: 2px dashed red;
}

input:invalid:required {
  background-image: linear-gradient(to right, pink, lightgreen);
}

input:valid {
  border: 2px solid black;
}

此CSS会使输入无效时输入具有红色虚线边框,而有效时则使输入具有更细的黑色实线边框. 当需要输入输入无效时,我们还添加了背景渐变. 在下面的示例中尝试新行为:

注意 :您可以在GitHub上以fruit-validation.html的形式找到此示例(另请参见源代码 .)

尝试提交没有价值的表格. 请注意无效输入如何获得焦点,出现默认错误消息("请填写此字段"),并阻止发送表单.

任何支持此属性的元素上都存在required属性,这意味着该元素是否与:required伪类匹配,无论它是否具有值. 如果<input>没有值,则input将匹配:invalid伪类.

注意 :为了获得良好的用户体验,请在需要表单字段时向用户指示. WCAG 无障碍指南要求这不仅是良好的用户体验. 另外,仅要求用户输入您实际需要的数据:例如,为什么您真的需要知道某人的性别或头衔?

Validating against a regular expression

另一个有用的验证功能是pattern属性,该属性期望使用正则表达式作为其值. 正则表达式(regex)是一种可用于匹配文本字符串中字符组合的模式,因此正则表达式非常适合表单验证,并可以在JavaScript中用于多种其他用途.

正则表达式非常复杂,我们不打算在本文中详尽地教您. 以下是一些示例,可让您基本了解它们的工作原理.

  • a —匹配一个是a字符(不是b ,不是aa ,依此类推).
  • abc —匹配a ,后跟b ,后跟c .
  • ab?c匹配a ,可选地后跟单个b ,后跟c . ( acabc
  • ab*c匹配a ,可选地后跟任意数量的b ,后跟c . ( acabcabbbbbc等).
  • a|b —匹配一个字符ab .
  • abc|xyz —完全匹配abc或完全xyz (但不abcxyzay ,依此类推).

在此我们不讨论更多的可能性. 有关完整列表和许多示例,请查阅我们的正则表达式文档.

让我们实现一个例子. 更新您的HTML以添加如下pattern属性:

<form>
  <label for="choose">Would you prefer a banana or a cherry?</label>
  <input id="choose" name="i_like" required pattern="[Bb]anana|[Cc]herry">
  <button>Submit</button>
</form>

这为我们提供了以下更新-试用:

注意 :您可以在GitHub上以fruit-pattern.html的形式找到此示例(另请参见源代码 .)

In this example, the <input> element accepts one of four possible values: the strings "banana", "Banana", "cherry", or "Cherry". Regular expressions are case-sensitive, but we've made it support capitalized as well as lower-case versions using an extra "Aa" pattern nested inside square brackets.

此时,请尝试将pattern属性内的值更改为等于您先前看到的一些示例,并查看这如何影响您可以输入的值以使输入值有效. 尝试自己编写一些内容,然后看看它如何进行. 尽可能使它们与水果相关,以便您的示例有意义!

如果<input>的非空值与正则表达式的模式不匹配,则input将匹配:invalid伪类.

注意:某些<input>元素类型不需要将pattern属性针对正则表达式进行验证. 例如,指定email类型可根据格式正确的电子邮件地址模式或与逗号分隔的电子邮件地址列表(如果具有multiple属性)匹配的模式来验证输入值.

注意<textarea>元素不支持pattern属性.

Constraining the length of your entries

您可以使用minlengthmaxlength属性来限制<input><textarea>创建的所有文本字段的字符长度. 如果字段具有值,并且该字段的字符数少于minlength值或大于maxlength值,则该字段无效.

浏览器通常不允许用户在文本字段中输入比预期更长的值. 与仅使用maxlength相比,更好的用户体验还在于以可访问的方式提供字符计数反馈,并让他们按比例缩小内容. 例如,在Twitter上发推时看到的字符限制. JavaScript(包括使用maxlength解决方案)可用于提供此功能

Constraining the values of your entries

对于数字字段(即<input type="number"> ),可以使用minmax属性来提供一系列有效值. 如果该字段包含的值超出此范围,则它将无效.

让我们看另一个例子. 创建fruit-start.html文件的新副本.

现在删除<body>元素的内容,并将其替换为以下内容:

<form>
  <div>
    <label for="choose">Would you prefer a banana or a cherry?</label>
    <input type="text" id="choose" name="i_like" required minlength="6" maxlength="6">
  </div>
  <div>
    <label for="number">How many would you like?</label>
    <input type="number" id="number" name="amount" value="1" min="1" max="10">
  </div>
  <div>
    <button>Submit</button>
  </div>
</form>
  • 在这里你可以看到,我们已经给出的text字段中minlengthmaxlength六,这是相同的长度,香蕉和樱桃.
  • 我们还将number字段的min为1, max为10. 输入的数字超出此范围将显示为无效; 用户将无法使用递增/递减箭头将值移到该范围之外. 如果用户手动输入此范围之外的数字,则数据无效. 该数字不是必需的,因此删除该值仍将得到一个有效值.

这是实时运行的示例:

注意 :您可以在GitHub上以fruit-length.html的形式找到此示例(另请参见源代码 .)

注意<input type="number"> (以及其他类型,例如rangedate )也可以采用step属性,该属性指定当使用输入控件时(例如,上下数字按钮). 在上面的示例中,我们没有包含step属性,因此该值默认为1 . 这意味着浮点数(如3.2)也将显示为无效.

Full example

Here is a full example to show usage of HTML's built-in validation features. First, some HTML:

<form>
  <p>
    <fieldset>
      <legend>Do you have a driver's license?<abbr title="This field is mandatory" aria-label="required">*</abbr></legend>
      <!-- While only one radio button in a same-named group can be selected at a time,
           and therefore only one radio button in a same-named group having the "required"
           attribute suffices in making a selection a requirement --> 
      <input type="radio" required name="driver" id="r1" value="yes"><label for="r1">Yes</label>
      <input type="radio" required name="driver" id="r2" value="no"><label for="r2">No</label>
    </fieldset>
  </p>
  <p>
    <label for="n1">How old are you?</label>
    <!-- The pattern attribute can act as a fallback for browsers which
         don't implement the number input type but support the pattern attribute.
         Please note that browsers that support the pattern attribute will make it
         fail silently when used with a number field.
         Its usage here acts only as a fallback -->
    <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" aria-label="required">*</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 address?</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>

现在使用一些CSS样式化HTML:

form {
  font: 1em sans-serif;
  max-width: 320px;
}

p > label {
  display: block;
}

input[type="text"],
input[type="email"],
input[type="number"],
textarea,
fieldset {
  width : 100%;
  border: 1px solid #333;
  box-sizing: border-box;
}

input:invalid {
  box-shadow: 0 0 5px 1px red;
}

input:focus:invalid {
  box-shadow: none;
}

结果如下:

有关可用于约束输入值和支持它们的输入类型的属性的完整列表,请参见与验证相关的属性 .

注意 :您可以在GitHub上找到该示例,网址为full-example.html (另请参见源代码 .)

Validating forms using JavaScript

如果要控制本机错误消息的外观或处理不支持HTML内置表单验证的旧版浏览器,则必须使用JavaScript. 在本节中,我们将介绍执行此操作的不同方法.

The Constraint Validation API

大多数浏览器都支持Constraint Validation API ,该API由以下表单元素DOM接口上可用的一组方法和属性组成:

约束验证API在上述元素上提供了以下属性.

  • validationMessage :返回一个本地化消息,描述控件不满足的验证约束(如果有). 如果控件不是约束验证的候选者( willValidatefalse )或元素的值满足其约束(有效),则将返回一个空字符串.
  • validity :返回一个ValidityState对象,该对象包含描述元素有效性状态的多个属性. 您可以在ValidityState参考页中找到所有可用属性的完整详细信息. 下面列出了一些较常见的:
    • patternMismatch :如果值与指定的pattern不匹配,则返回true如果匹配,则返回false . 如果为true,则元素与:invalid CSS伪类匹配.
    • tooLong :如果该值大于maxlength属性指定的最大长度,则返回true如果小于或等于最大值,则返回false . 如果为true,则元素与:invalid CSS伪类匹配.
    • tooShort :返回true如果该值小于由规定的最小长度短minlength属性,或false ,如果它大于或等于最小. 如果为true,则元素与:invalid CSS伪类匹配.
    • rangeOverflow :如果该值大于max属性指定的max ,则返回true如果小于或等于max ,则返回false . 如果为true,则元素匹配:invalid:out-of-range CSS伪类.
    • rangeUnderflow :如果值小于min属性指定的最小值,则返回true如果值大于或等于最小值,则返回false . 如果为true,则元素匹配:invalid:out-of-range CSS伪类.
    • typeMismatch :如果该值不是必需的语法( typeemailurl ),则返回true如果语法true则返回false . 如果为true ,则元素匹配:invalid CSS伪类.
    • valid :如果元素满足其所有验证约束,并因此被视为有效,则返回true否则,如果未通过任何约束,则返回false . 如果为true,则元素匹配:valid CSS伪类;否则为false. :invalid CSS伪类,否则.
    • valueMissing :如果元素具有required属性但没有值,则返回true否则返回false . 如果为true,则元素与:invalid CSS伪类匹配.
  • willValidate :如果在提交表单时将对元素进行验证,则返回true否则,返回true . 否则为false .

约束验证API还可在上述元素上提供以下方法.

  • checkValidity() :如果元素的值没有有效性问题,则返回true否则,返回true . 否则为false . 如果该元素无效,则此方法还将在该元素上引发invalid事件 .
  • setCustomValidity( message ) :向元素添加自定义错误消息; 如果设置了自定义错误消息,则认为该元素无效,并显示指定的错误. 这使您可以使用JavaScript代码建立验证失败,而不是由标准HTML5验证约束提供的验证失败. 报告问题时,该消息将显示给用户.

Implementing a customized error message

正如您在前面的HTML5验证约束示例中所看到的,每次用户尝试提交无效的表单时,浏览器都会显示一条错误消息. 此消息的显示方式取决于浏览器.

这些自动消息有两个缺点:

  • 没有标准的方法来更改CSS的外观.
  • 它们取决于浏览器的语言环境,这意味着您可以使用一种语言显示页面,而用另一种语言显示错误消息,如以下Firefox屏幕截图所示.

Example of an error message with Firefox in French on an English page

自定义这些错误消息是约束验证API的最常见用例之一. 让我们来看一个简单的示例.

我们将从一些简单的HTML开始(随意将其放在空白HTML文件中;如果愿意,可以使用fruit-start.html的新副本作为基础):

<form>
  <label for="mail">I would like you to provide me with an e-mail address:</label>
  <input type="email" id="mail" name="mail">
  <button>Submit</button>
</form>

并将以下JavaScript添加到页面:

const email = document.getElementById("mail");

email.addEventListener("input", function (event) {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an e-mail address!");
  } else {
    email.setCustomValidity("");
  }
});

在这里,我们存储对电子邮件输入的引用,然后向其中添加一个事件侦听器,该事件侦听器在每次更改输入内部的值时运行包含的代码.

在包含的代码内部,我们检查电子邮件输入的validity.typeMismatch属性是否返回true ,这意味着包含的值与格式正确的电子邮件地址的模式不匹配. 如果是这样,我们通过自定义消息调用setCustomValidity()方法. 这将使输入无效,因此当您尝试提交表单时,提交将失败并显示自定义错误消息.

如果validity.typeMismatch属性返回false ,则将setCustomValidity()方法称为空字符串. 这将使输入有效,因此将提交表单.

您可以在下面尝试:

注意 :您可以在GitHub上以custom-error-message.html的形式找到此示例(另请参见源代码 .)

A more detailed example

现在,我们已经看到了一个非常简单的示例,让我们看看如何使用此API构建一些稍微复杂的自定义验证.

首先是HTML. 再次,随时与我们一起构建它:

<form novalidate>
  <p>
    <label for="mail">
      <span>Please enter an email address:</span>
      <input type="email" id="mail" name="mail" required minlength="8">
      <span class="error" aria-live="polite"></span>
    </label>
  </p>
  <button>Submit</button>
</form>

This simple form uses the novalidate attribute to turn off the browser's automatic validation; 这种简单的形式使用novalidate属性来关闭浏览器的自动验证. this lets our script take control over validation. 这使我们的脚本可以控制验证. However, this doesn't disable support for the constraint validation API nor the application of CSS pseudo-classes like :valid , etc. That means that even though the browser doesn't automatically check the validity of the form before sending its data, you can still do it yourself and style the form accordingly. 但是,这不会禁用对约束验证API的支持,也不会禁用CSS伪类的应用程序,如:valid等.这意味着,即使浏览器在发送数据之前不会自动检查表单的有效性,仍然可以自己做,并相应地设置表单样式.

我们要验证的<input type="email"><input type="email"> ,这是required ,并且minlength为8个字符. 让我们使用自己的代码进行检查,并为每个代码显示一条自定义错误消息.

我们旨在在<span>元素内显示错误消息. 在该<span>上设置aria-live属性,以确保将我们的自定义错误消息显示给所有人,包括将其显示给屏幕阅读器用户.

注意 :这里的关键是,在表单上设置novalidate属性可以阻止表单显示其自身的错误消息气泡,并允许我们以自己选择的方式在DOM中显示自定义错误消息.

现在使用一些基本的CSS来稍微改善表单的外观,并在输入数据无效时提供一些视觉反馈:

body {
  font: 1em sans-serif;
  width: 200px;
  padding: 0;
  margin : 0 auto;
}

p * {
  display: block;
}

input[type=email]{
  -webkit-appearance: none;
  appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  box-sizing: border-box;
}

/* This is our style for the invalid fields */
input:invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus:invalid {
  outline: none;
}

/* This is the style of our error messages */
.error {
  width  : 100%;
  padding: 0;

  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;

  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}

现在,让我们看一下实现自定义错误验证的JavaScript.

// There are many ways to pick a DOM node; here we get the form itself and the email
// input box, as well as the span element into which we will place the error message.
const form  = document.getElementsByTagName('form')[0];

const email = document.getElementById('mail');
const emailError = document.querySelector('#mail + span.error');

email.addEventListener('input', function (event) {
  // Each time the user types something, we check if the
  // form fields are valid.

  if (email.validity.valid) {
    // In case there is an error message visible, if the field
    // is valid, we remove the error message.
    emailError.innerHTML = ''; // Reset the content of the message
    emailError.className = 'error'; // Reset the visual state of the message
  } else {
    // If there is still an error, show the correct error
    showError();
  }
});

form.addEventListener('submit', function (event) {
  // if the email field is valid, we let the form submit

  if(!email.validity.valid) {
    // If it isn't, we display an appropriate error message
    showError();
    // Then we prevent the form from being sent by canceling the event
    event.preventDefault();
  }
});

function showError() {
  if(email.validity.valueMissing) {
    // If the field is empty
    // display the following error message.
    emailError.textContent = 'You need to enter an e-mail address.';
  } else if(email.validity.typeMismatch) {
    // If the field doesn't contain an email address
    // display the following error message.
    emailError.textContent = 'Entered value needs to be an e-mail address.';
  } else if(email.validity.tooShort) {
    // If the data is too short
    // display the following error message.
    emailError.textContent = `Email should be at least ${ email.minLength } characters; you entered ${ email.value.length }.`;
  }

  // Set the styling appropriately
  emailError.className = 'error active';
}

这些评论很好地解释了事情,但是简短地:

  • 每次我们更改输入的值时,我们都会检查它是否包含有效数据. 如果有,我们将删除所有显示的错误消息. 如果数据无效,则运行showError()以显示适当的错误.
  • Every time we try to submit the form, we again check to see if the data is valid. If so, we let the form submit. If not, we run showError() to show the appropriate error, and stop the form submitting with preventDefault().
  • showError()函数使用输入的validity对象的各种属性来确定错误是什么,然后适当地显示错误消息.

这是实时结果:

Note: You can find this example live on GitHub as detailed-custom-validation.html (see also the source code.)

约束验证API为您提供了一个强大的工具来处理表单验证,使您对用户界面具有极大的控制权,而不仅仅是单独使用HTML和CSS所能做的.

注意 :有关更多信息,请参阅我们的约束验证指南约束验证API参考.

Validating forms without a built-in API

在某些情况下,例如旧版浏览器支持或自定义控件 ,您将无法使用或不想使用Constraint Validation API.您仍然可以使用JavaScript来验证表单,但您只需要必须自己写.

要验证表单,请问自己几个问题:

What kind of validation should I perform?
您需要确定如何验证数据:字符串操作,类型转换,正则表达式等. 由你决定.
What should I do if the form doesn't validate?
这显然是UI问题. 您必须决定表单的行为方式. 表格是否仍发送数据? 您应该突出显示错误的字段吗? 您应该显示错误消息吗?
How can I help the user to correct invalid data?
为了减少用户的挫败感,提供尽可能多的有用信息以指导他们纠正输入很重要. 您应该提供前期建议,以便他们知道预期的结果,并清除错误消息. 如果您想深入了解表单验证UI要求,请阅读以下有用的文章:

An example that doesn't use the constraint validation API

为了说明这一点,以下是与旧版浏览器一起使用的上一示例的简化版本.

HTML几乎是相同的. 我们只是删除了HTML验证功能.

<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>
  <!-- Some legacy browsers need to have the `type` attribute
       explicitly set to `submit` on the `button`element -->
  <button type="submit">Submit</button>
</form>

同样,CSS不需要太多更改. 我们刚刚将:invalid CSS伪类转换为真实类,并避免使用在Internet Explorer 6上不起作用的属性选择器.

body {
  font: 1em sans-serif;
  width: 200px;
  padding: 0;
  margin : 0 auto;
}

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%;

  box-sizing: border-box;
}

/* This is our style for the invalid fields */
input.invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus.invalid {
  outline: none;
}

/* This is the style of our error messages */
.error {
  width  : 100%;
  padding: 0;
 
  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}

最大的变化在于JavaScript代码,这需要做很多繁重的工作.

// There are fewer ways to pick a DOM node with legacy browsers
const form  = document.getElementsByTagName('form')[0];
const email = document.getElementById('mail');

// The following is a trick to reach the next sibling Element node in the DOM
// This is dangerous because you can easily build an infinite loop.
// In modern browsers, you should prefer using element.nextElementSibling
let error = email;
while ((error = error.nextSibling).nodeType != 1);

// As per the HTML5 Specification
const emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

// Many legacy browsers do not support the addEventListener method.
// Here is a simple way to handle this; it's far from the only one.
function addEvent(element, event, callback) {
  let previousEventCallBack = element["on"+event];
  element["on"+event] = function (e) {
    const output = callback(e);

    // A callback that returns `false` stops the callback chain
    // and interrupts the execution of the event callback.
    if (output === false) return false;

    if (typeof previousEventCallBack === 'function') {
      output = previousEventCallBack(e);
      if(output === false) return false;
    }
  }
};

// Now we can rebuild our validation constraint
// Because we do not rely on CSS pseudo-class, we have to 
// explicitly set the valid/invalid class on our email field
addEvent(window, "load", function () {
  // Here, we test if the field is empty (remember, the field is not required)
  // If it is not, we check if its content is a well-formed e-mail address.
  const test = email.value.length === 0 || emailRegExp.test(email.value);

  email.className = test ? "valid" : "invalid";
});

// This defines what happens when the user types in the field
addEvent(email, "input", function () {
  const test = email.value.length === 0 || emailRegExp.test(email.value);
  if (test) {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  } else {
    email.className = "invalid";
  }
});

// This defines what happens when the user tries to submit the data
addEvent(form, "submit", function () {
  const 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";

    // Some legacy browsers do not support the event.preventDefault() method
    return false;
  } else {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  }
});

结果看起来像这样:

如您所见,自行构建验证系统并不难. 困难的部分是使其足够通用,以同时使用跨平台和您可能创建的任何形式. 有许多可用于执行表单验证的库,例如Validate.js .

Summary

如果您要自定义样式和错误消息,则有时需要使用JavaScript进行客户端表单验证,但始终需要您仔细考虑用户. 永远记住要帮助您的用户更正他们提供的数据. 为此,请确保:

  • 显示明确的错误消息.
  • 允许输入格式.
  • 指出错误发生的确切位置,特别是在大型表格上.

一旦您检查了正确填写的表格,就可以提交表格. 接下来我们将介绍发送表单数据 .

In this module

Advanced Topics