【譯】表單數據校驗

當你須要常常在服務器上校驗數據時,在Web頁面上的另加校驗就有諸多好處。多數狀況下,用戶會被表單惹惱。而當用戶填完表單時就校驗數據,既有助於用戶當即發現他們犯的錯誤,也能減小等待HTTP響應的時間、並減小服務器對錯誤表單輸入的處理。本文介紹的,就是如何在表單所在頁面對錶單數據進行校驗。java

使用瀏覽器內置的表單校驗

HTML5的一大特性,就是在不依賴腳本的狀況下校驗大多數的用戶數據。該特性是經過使用表單元素上的校驗特性來實現的。node

元素校驗不經過時

當一個元素校驗不經過時,會發生兩件事:jquery

  • 該元素會匹配到:invalid這個CSS僞類,它能讓你給校驗不過的元素提供特定的樣式。相似地,校驗經過的元素則會匹配:valid僞類。git

  • 當用戶要發送數據時,瀏覽器會阻止這個表單、並展現一段錯誤信息。github

<input>元素的校驗約束

全部的<input>元素都能使用pattern特性來做校驗。該特性採用一個區分大小寫的正則表達式做爲它的值。若元素的值非空、或者不匹配該特性所指定的正則表達式,該元素就會被認爲是校驗不經過的。web

舉個例子

<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"。segmentfault

required特性

若某個元素須要在提交表單以前填一個值,那咱們能夠經過required特性來標記該元素。當該特性爲true時,所在的文本域不容許爲空。

<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>元素的type特性被設爲emailurl時,無需再使用pattern特性來坐校驗。指定email類型時,文本框的值就得是個正確格式的email地址(同時指定mutiple特性時,還能夠是一個逗號分隔的email地址列表)。指定url類型的文本框則會自動匹配一個URL。

其餘校驗約束

全部接受用戶輸入的表單元素(<textarea>, <select>等)都支持required特性,但要注意<textarea>元素並不支持pattern特性。

全部的文本輸入框(<input><textarea>)均可以使用maxlength特性來約束文本大小。輸入框的值若大於maxlength指定的值,就會校驗不經過。可是瀏覽器一般不會讓用戶輸入超過文本框指定長度的文本。

對於數字輸入框,minmax特性也提供了校驗約束。若輸入框的值小於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>
    <!-- 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>
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來檢查和定製表單元素的狀態。此外它也能改變錯誤信息的文本。來看個例子:

<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

如今愈來愈多的瀏覽器開始支持約束驗證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以下:

<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用於裝飾咱們的表單、並讓錯誤的輸出看起來更吸引人些。

/* 這段只是用來美化表單 */
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代碼用來處理咱們自定義的錯誤校驗。

// 獲取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的例子

爲了更好地說明,咱們接下來要重構上述的例子,讓它能在老舊瀏覽器上運行:

<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不支持的特性選擇器便可。

/* 這段只是用來美化表單 */
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代碼的改變最大,爲了兼容咱們須要作更多的事情。

// 老舊瀏覽器上獲取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,但它要求咱們認真爲用戶着想,始終得幫用戶糾正他們輸入的數據。要作到這點,請確保:

  • 展現明確的錯誤信息

  • 約定好輸入格式

  • 指出錯誤出現的確切位置(特別是在那些巨大的表單中)

相關文章
相關標籤/搜索