表单验证

这篇翻译不完整。请帮忙从英语翻译这篇文章

  

这篇文章主要讲述了如何利用网页自身内容进行表单验证。

在很多情况下,一些表单设计确实惹怒了用户。如果你在服务器端验证表单的同时,在浏览器端的也做表单验证,这会让用户体验提升不少。通过网页客户端的验证,在用户完成表单数据录入后,会立即看到自己可能出现的录入错误,这样就避免了等待服务器验证并返回验证错误的等待时间。

浏览器支持的表单验证

HTML5的一个特征是不依赖脚本实现表单验证。它的这种能力主要是使用表单元素的约束-验证属性。

当一个元素是无效的

当一个元素是无效的,会触发一下两个事情:

  • 这个元素会添加 :invalid CSS伪类,这个元素将按照 .invalid 伪类展示外观。类似的,有效的元素外观将按照 :valid 伪类展示。
  • 这时,如果用户尝试发送数据,浏览器将锁定表单并提示错误信息。

"input"表单元素的约束-验证

所有 input表单元素都可以用“pattern”属性验证约束。“pattern”属性正则表达规定了元素的值,并且是大小敏感。如果一个元素的值非空并且无法与“pattern”的正则表达匹配,则这个元素无效。

一个简单的例子

<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>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

在这个例子中,“input”属性元素接受的值为:空值或“banana”或“cherry”

 “required”属性

如果一个元素需要提交一个值到服务器,你可以为这个元素添加用“required”属性。当元素含有“required”属性时,这个元素的值是不可以为空的。

<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>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

你可以看到,这时input表单域的边界和上一个例子中的不同。

注: “input”元素的“type”属性设置为“email”或“url”时,不需要“pattern”属性来验证。指定了“email”或“url”类型的“input”元素,客户端会以邮箱或URL地址的标准要求“input”值的内容。

其它的约束-验证

所有的表单域元素都支持“required”属性,但是并不是全都支持“pattern”属性,如“textarea”。

所有的文本域元素都支持“maxlength”属性,该属性可以限定元素值长度的上限。这个属性经常用于不希望用户输入过多的数据的情况。

对于数字型元素,“min”和“max”属性约束值的最小值和最大值。

下面是一个包含全部情况的例子:

<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>
    <!-- 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">*</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>
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 {
/* required to properly style form 
   elements on WebKit based browsers */
  -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样式。
  • 显示的最终信息依据当前的浏览器,这时候会出现网页是一种语言而错误提示信息是另一种语言。


发问浏览器版本显示的英文网页

Browser Rendering
Firefox 17 (Windows 7) Example of an error message with Firefox in French on an English page
Chrome 22 (Windows 7) Example of an error message with Chrome in French on an English page
Opera 12.10 (Mac OSX) Example of an error message with Opera in French on an English page

我们无法使用HTM和CCS来定制错误提示信息,除非用JavaScript脚本。

HTML5提供了检查和修改表单元素状态的约束-验证API。在一些例子中,我们使可以改变错误提醒信息。让我们看一个简单的例子:

<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() 方法:

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

随着越来越多的浏览器支持HTML5的约束-验证API,它也变得更可靠。这些API对于任一个表单属性包含一系列方法和属性。 

约束-验证API属性

属性 描述
validationMessage 验证信息。如果有任何未通过验证的元素,它是一个本地化的验证描述信息;如果约束验证状态为false(表单元素将不经过约束验证)或表单元素通过约束验证,他是一个空字符串。
validity 一个验证状态(ValidityState)对象,用来描述元素的验证状态。
validity.customError 如果自定义验证方法出错返回“true”,否则返回“false”。
validity.patternMismatch 如果元素值和要求的正则表达规范不匹配返回“true”,否则返回‘’false”。

如果返回值为“true”,则元素按 :invalid CSS伪类显示外观。
validity.rangeOverflow 如何元素的值大于元素要求的最大值返回“true”,否则返回“false”。

如果返回值为“true”,则元素按:invalid and :out-of-range 和 CSS 伪类显示外观。
validity.rangeUnderflow 如果元素值小于元素要求的最小值返回“true”,否则返回“false”。

如果返回值为“true”,则元素按 :invalid:out-of-range CSS 伪类显示外观。
validity.stepMismatch 如果元素的值的改变幅度不符合元素步幅要求,则返回“true”,否则返回“false”。

如果返回值为"true",元素则按 :invalid:out-of-range CSS 伪类显示外观。
validity.tooLong 如果元素的值的长度大于要求的最大长度,返回“true”;否则返回“false”

如果返回“true”,则元素按 :invalid:out-of-range CSS 伪类显示外观。
validity.typeMismatch 如果元素的值不符合语法,返回“true”;否则返回“false”。

如果返回值为“true”,则元素将按:invalid css伪类显示外观。
validity.valid 如何元素值通过了验证,则返回“true”;否则返回“false”。

如果返回“true”,则元素按 :valid css伪类显示外观。;否则按:invalid CSS 伪类显示外观。。
validity.valueMissing 如果元素要求非空,而元素是没有值时,返回“true”,否则返回“false”。

如果返回值为“true,则元素按 :invalid CSS伪类显示外观。。
willValidate 在表单提交时,如果元素将要进行约束验证返回“true”,否则返回“false”。

约束-验证 API 方法

方法 描述
checkValidity() 如果表单元素都通过验证,则返回“true”;否则返回"false"。如果元素是无效的,这个方法将触发这个元素的invalid 事件。
setCustomValidity(message) 如何你设置了“validity.customError”,通过这个方法可以添加错误提示信息,元素自身的错误提示将会关闭。这个方法让你用JavaScript脚本代码代替标准约束-验证API来建立约束-验证。参数“message”是向用户报告错误的问题。

如果参数是空字符串,定制错误功能将被取消。

对于遗留的浏览器,我们可以用H5F来弥补老浏览器对HTML5自带约束-验证API支持的不足。现在你已经使用过了JavaScript,对你的网站的设计和实现使用JavaScript插件并不是一个负担。

使用约束-验证API的例子

让我们看看如何使用这些API来定制错误提示消息。首先,我们看下HTML:

<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”的form属性关闭表单自动约束-验证功能;这里使用我们自己脚本代码进行表单验证。关闭了表单自身的验证功能,并不影响表单的约束-验证API和一些样式伪类的使用,如:valid:invalid, :in-range:out-of-range 。 尽管浏览器不自动验证提交的表单数据,我们仍可自己验证。

我们可以使用 aria-live 属性保障我们的定制错误对所有人都是有效的,包括使用辅助技术的人,如使用屏幕阅读器的人。

现在我们看看CSS代码

下面的CSS样式,确立表单和错误提示的外观,错误的样式看起来更有吸引力。

/* This is just to make the example nicer */
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;
}

/* 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;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

下面的 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.

var form  = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');
var error = document.querySelector('.error');

email.addEventListener("keyup", function (event) {
  // Each time the user types something, we check if the
  // email field is valid.
  if (email.validity.valid) {
    // In case there is an error message visible, if the field
    // is valid, we remove the error message.
    error.innerHTML = ""; // Reset the content of the message
    error.className = "error"; // Reset the visual state of the message
  }
}, false);
form.addEventListener("submit", function (event) {
  // Each time the user tries to send the data, we check
  // if the email field is valid.
  if (!email.validity.valid) {
    
    // If the field is not valid, we display a custom
    // error message.
    error.innerHTML = "I expect an e-mail, darling!";
    error.className = "error active";
    // And we prevent the form from being sent by canceling the event
    event.preventDefault();
  }
}, false);

这是个在线例子:

与只用HTML和CSS相比,约束-验证API为我们提供了强大的表单验证工具,让你拥有对用户界面更多控制力。

不用内置API验证表单

对于老旧的浏览器或自定义表单控件,你不能使用或者不想使用内置API验证表单。在这种情况下,你仍旧可以使用JavaScript来验证表单。验证表单的目的更多的是从用户角度来实现而不是对数据的验证。

为了验证表单,你需要问自己几个问题;

表单应该做什么类型的验证?
你需要决定如何验证你的数据:字符串操作,类型转换,正则表达等等。这都取决于你。你只需要记得表单数据的格式是文本,JavaScript操作的数据是字符串。
当表单验证不通过时,表单应该怎么做?
这个明显是用户界面的问题。你需要决定表单应该做出什么样的反应:表单数据是否继续提交?出现错误的表单域是否要高亮提醒?是不是应该给出错误提示信息?
我如何帮助用户改正无效数据?
当用户录入数据时,为了减少用户的困惑,我们应该给用户提供尽可能多的帮助信息,这对他们完成这项工作是非常重要的。我们要像提供错误提醒信息一样,清晰地给出表单域所需数据的建议。

如何你想深入挖掘表单验证用户界面的设计需求,这里有一些有用的文章供你阅读:

不适用内置约束-验证API的例子

为了说清楚这个问题,让我们重构之前的例子,这样就可以适用于老旧浏览器了。

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

正如你看到的,HTML代码部分几乎一样;我们仅仅去掉了HTML5部分。请注意 我们保留了ARIA,它是一个独立的规范,并不依附于HTML5。

CSS

类似的,CSS代码不需要更改太多; 我们仅仅把 :invalid 伪类改成了真实的类,注意就避免了不适用于Internet Explorer 6的属性选择器功能。

/* This is just to make the example nicer */
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;
}

/* 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;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

最大的变化是JavaScript代码,js代码需要做很多累活。

// There are fewer ways to pick a DOM node with legacy browsers
var form  = document.getElementsByTagName('form')[0];
var 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
var error = email;
while ((error = error.nextSibling).nodeType != 1);

// As per the HTML5 Specification
var 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) {
  var previousEventCallBack = element["on"+event];
  element["on"+event] = function (e) {
    var 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.
  var 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, "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";
  }
});

// This defines what happens when the user tries to submit the data
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";

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

结果如下面所示:

正如你所见,自己做一个验证系统并不困难。困难的地方在于如何使它具有通用性——适用于跨平台和不同表单。目前有很多类库来实现表单验证;你不要做任何犹豫使用下面展示的你类库。

异步验证

有些例子中用异步验证非常有用。 当用户填写的数据与服务器端的数据紧密相关,使用异步验证非常必要。一个使用案例是用户注册表单,需要录入用户名称。为了避免重复,我们在用户提交数据前利用AJAX请求检查用户的可用性比用户提交数据后,再返回来告诉用户用户名不可用更人性化。

采用这样的验证方式需要考虑下面的问题:

  • 数据安全问题。异步验证需要显式的使用API和数据,请确保所使用的数据是非敏感的。
  • 网络性能问题。较差的网络会拖慢异步验证,这需要界面设计工作者确保当异步验证失效时,网页不拥塞。

结论

表单验证不需要复杂的JavaScript代码,但需要良好的考虑用户体验。面对用户提供的数据你要一直保持清醒,不要完全相信任何一个用户数据。总而言之,请确定:

  • 展示清晰的错误信息。
  • 不要相信用户输入的数据。对用户提供的数据保持消极态度。
  • 精确定位发生错误的位置(尤其是面对j较大的表单时)。

文档标签和贡献者

 此页面的贡献者: liu-xiao-cui, jileieli
 最后编辑者: liu-xiao-cui,