客戶端表單驗證

在將資料提交到伺服器之前,務必確保所有必需的表單控制元件都已填寫,並且格式正確。這稱為**客戶端表單驗證**,有助於確保提交的資料與各種表單控制元件中設定的要求相匹配。本文將引導您瞭解客戶端表單驗證的基本概念和示例。

先決條件 計算機知識,對HTMLCSSJavaScript有基本的瞭解。
目標 瞭解什麼是客戶端表單驗證、為什麼它很重要以及如何應用各種技術來實現它。

客戶端驗證是一個初步檢查,也是良好使用者體驗的重要功能;透過在客戶端捕獲無效資料,使用者可以立即修復它。如果它到達伺服器然後被拒絕,則由於往返伺服器然後返回客戶端以告知使用者修復其資料而導致明顯的延遲。

但是,**客戶端驗證不應被視為**一項詳盡的安全措施!您的應用程式應始終對客戶端和**伺服器端**的任何表單提交資料執行安全檢查,因為客戶端驗證很容易繞過,因此惡意使用者仍然可以輕鬆地將錯誤資料傳送到您的伺服器。閱讀網站安全以瞭解可能發生的情況;實現伺服器端驗證超出了本模組的範圍,但您應該牢記這一點。

什麼是表單驗證?

訪問任何帶有登錄檔單的熱門網站,您會注意到當您未按預期格式輸入資料時,它們會提供反饋。您會收到以下訊息

  • "此欄位是必需的"(您不能將此欄位留空)。
  • "請以 xxx-xxxx 的格式輸入您的電話號碼"(需要特定的資料格式才能被視為有效)。
  • "請輸入有效的電子郵件地址"(您輸入的資料格式不正確)。
  • "您的密碼需要在 8 到 30 個字元之間,並且包含一個大寫字母、一個符號和一個數字。"(您的資料需要非常特定的資料格式)。

這稱為**表單驗證**。當您輸入資料時,瀏覽器和/或 Web 伺服器將檢查資料是否格式正確並在應用程式設定的約束範圍內。在瀏覽器中完成的驗證稱為**客戶端**驗證,而在伺服器上完成的驗證稱為**伺服器端**驗證。在本章中,我們將重點關注客戶端驗證。

如果資訊格式正確,則應用程式允許將資料提交到伺服器(通常)並儲存在資料庫中;如果資訊格式不正確,則會向用戶顯示錯誤訊息,說明需要更正的內容,並讓他們重試。

我們希望使填寫 Web 表單儘可能容易。那麼,為什麼我們堅持要驗證我們的表單呢?主要有三個原因

  • **我們希望獲得正確格式的正確資料。**如果使用者資料以錯誤的格式儲存、不正確或完全省略,我們的應用程式將無法正常工作。
  • **我們希望保護使用者的資料。**強制使用者輸入安全密碼可以更輕鬆地保護其帳戶資訊。
  • **我們希望保護自己。**惡意使用者可以使用許多方法濫用未受保護的表單來損害應用程式。請參閱網站安全

    **警告:**永遠不要信任從客戶端傳遞到伺服器的資料。即使您的表單正在正確驗證並防止在客戶端輸入錯誤格式的資料,惡意使用者仍然可以更改網路請求。

客戶端驗證的不同型別

在 Web 上,您會遇到兩種不同的客戶端驗證型別

  • **內建表單驗證**使用 HTML 表單驗證功能,我們在本模組的許多地方都討論過。此驗證通常不需要太多 JavaScript。內建表單驗證的效能優於 JavaScript,但它不如 JavaScript 驗證靈活。
  • **JavaScript** 驗證使用 JavaScript 編寫。此驗證完全可自定義,但您需要全部建立它(或使用庫)。

使用內建表單驗證

現代表單控制元件最重要的功能之一是能夠在不依賴 JavaScript 的情況下驗證大多數使用者資料。這是透過在表單元素上使用驗證屬性來完成的。我們在課程中已經見過很多這樣的屬性,但為了回顧一下

  • required:指定在提交表單之前是否需要填寫表單欄位。
  • minlengthmaxlength:指定文字資料(字串)的最小和最大長度。
  • minmax:指定數值輸入型別的最小值和最大值。
  • type:指定資料是否需要是數字、電子郵件地址或某些其他特定預設型別。
  • pattern:指定一個正則表示式,該表示式定義輸入資料需要遵循的模式。

如果表單欄位中輸入的資料遵循上述屬性指定的所有規則,則認為它是有效的。否則,則認為它是無效的。

當元素有效時,以下內容為真

  • 該元素與:valid CSS 偽類匹配,允許您對有效元素應用特定樣式。
  • 如果使用者嘗試傳送資料,瀏覽器將提交表單,前提是沒有其他阻止它這樣做的因素(例如,JavaScript)。

當元素無效時,以下內容為真

  • 該元素與:invalid CSS 偽類匹配,有時還與其他 UI 偽類匹配(例如,:out-of-range)具體取決於錯誤,這允許您對無效元素應用特定樣式。
  • 如果使用者嘗試傳送資料,瀏覽器將阻止表單並顯示錯誤訊息。

**注意:**有幾個錯誤會阻止表單提交,包括badInputpatternMismatchrangeOverflowrangeUnderflowstepMismatchtooLongtooShorttypeMismatchvalueMissingcustomError

內建表單驗證示例

在本節中,我們將測試上面討論的一些屬性。

簡單的起始檔案

讓我們從一個簡單的示例開始:一個允許您選擇更喜歡香蕉還是櫻桃的輸入。此示例涉及一個簡單的文字<input>,以及一個關聯的<label>和一個提交<button>。在 GitHub 上找到原始碼,網址為fruit-start.html,以及下面的即時示例。

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

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

首先,在硬碟驅動器上的新目錄中複製 fruit-start.html

required 屬性

最簡單的 HTML 驗證功能是required 屬性。要使輸入成為強制性輸入,請將此屬性新增到元素中。設定此屬性後,元素將與:required UI 偽類匹配,並且當輸入為空時,表單將不會提交,並在提交時顯示錯誤訊息。在為空時,輸入也將被視為無效,與:invalid UI 偽類匹配。

向您的輸入新增 required 屬性,如下所示。

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

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 可訪問性 指南的要求。此外,僅要求使用者輸入您實際需要的資料:例如,為什麼您真的需要知道某人的性別或職稱?

針對正則表示式進行驗證

另一個有用的驗證功能是pattern 屬性,它期望其值為正則表示式。正則表示式 (regexp) 是一種模式,可用於匹配文字字串中的字元組合,因此正則表示式非常適合表單驗證,並在 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 屬性,如下所示

html
<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(另請參閱 原始碼)。

在此示例中,<input> 元素接受四個可能值之一:字串“banana”、“Banana”、“cherry”或“Cherry”。正則表示式區分大小寫,但我們使用巢狀在方括號內的額外“Aa”模式使其支援大寫和小寫版本。

此時,嘗試將 pattern 屬性中的值更改為之前看到的一些示例,並檢視這如何影響您可以輸入的值以使輸入值有效。嘗試編寫一些您自己的示例,並檢視效果如何。儘可能使它們與水果相關,以便您的示例有意義!

如果 <input> 的非空值與正則表示式的模式不匹配,則 input 將匹配 :invalid 偽類。

注意:某些 <input> 元素型別不需要 pattern 屬性來針對正則表示式進行驗證。例如,指定 email 型別會根據格式良好的電子郵件地址模式或與電子郵件地址的逗號分隔列表匹配的模式驗證輸入值,如果它具有 multiple 屬性。

注意:<textarea> 元素不支援 pattern 屬性。

約束條目的長度

您可以使用 minlengthmaxlength 屬性來約束由 <input><textarea> 建立的所有文字欄位的字元長度。如果欄位具有值且該值的字元數少於 minlength 值或多於 maxlength 值,則該欄位無效。

瀏覽器通常不允許使用者在文字欄位中鍵入超出預期的更長值。比僅僅使用 maxlength 更好的使用者體驗是在可訪問的方式下提供字元計數反饋並讓他們將其內容編輯到合適的大小。社交媒體上發帖時的字元限制就是一個例子。可以使用 JavaScript(包括 使用 maxlength 的解決方案)來提供此功能。

注意:如果以程式設計方式設定值,則永遠不會報告長度約束。它們僅針對使用者提供的輸入報告。

約束條目的值

對於數字欄位(即 <input type="number">),可以使用 minmax 屬性提供有效值的範圍。如果欄位包含此範圍之外的值,則該欄位將無效。

讓我們看另一個例子。建立 fruit-start.html 檔案的新副本。

現在刪除 <body> 元素的內容,並將其替換為以下內容

html
<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 欄位提供了 6 的 minlengthmaxlength,這與 banana 和 cherry 的長度相同。
  • 我們還為 number 欄位提供了 1 的 min 和 10 的 max。在此範圍之外輸入的數字將顯示為無效;使用者將無法使用增量/減量箭頭將值移動到此範圍之外。如果使用者手動輸入此範圍之外的數字,則資料無效。數字不是必需的,因此刪除值仍將導致有效值。

以下是執行中的示例

注意:您可以在 GitHub 上找到此示例的即時版本,網址為 fruit-length.html。另請參閱 原始碼

注意:<input type="number">(以及其他型別,例如 rangedate)還可以使用 step 屬性,該屬性指定使用輸入控制元件(例如向上和向下數字按鈕)時值將增加或減少的增量。在上面的示例中,我們沒有包含 step 屬性,因此該值預設為 1。這意味著浮點數(如 3.2)也將顯示為無效。

完整示例

這是一個完整的示例,用於演示 HTML 內建驗證功能的使用。首先,一些 HTML

html
<form>
  <fieldset>
    <legend>
      Do you have a driver's license?<span aria-label="required">*</span>
    </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>
    <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?<span aria-label="required">*</span></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 email 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>

現在是一些用於設定 HTML 樣式的 CSS

css
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(另請參閱 原始碼)。

使用 JavaScript 驗證表單

如果您想控制原生錯誤訊息的外觀和感覺,則必須使用 JavaScript。在本節中,我們將瞭解執行此操作的不同方法。

約束驗證 API

約束驗證 API 由以下表單元素 DOM 介面上提供的一組方法和屬性組成

約束驗證 API 在上述元素上提供了以下屬性。

  • validationMessage:返回一個本地化訊息,描述控制元件不滿足的驗證約束(如果有)。如果控制元件不是約束驗證的候選物件(willValidatefalse)或元素的值滿足其約束(有效),則將返回空字串。
  • validity:返回一個 ValidityState 物件,其中包含幾個描述元素有效性狀態的屬性。您可以在 ValidityState 參考頁面中找到所有可用屬性的完整詳細資訊;下面列出了一些更常見的屬性
    • patternMismatch:如果值與指定的 pattern 不匹配,則返回 true,如果匹配,則返回 false。如果為 true,則元素匹配 :invalid CSS 偽類。
    • tooLong:如果值長於 maxlength 屬性指定的最大長度,則返回 true,如果短於或等於最大長度,則返回 false。如果為 true,則元素匹配 :invalid CSS 偽類。
    • tooShort:如果值短於 minlength 屬性指定的最小長度,則返回 true,如果長於或等於最小長度,則返回 false。如果為 true,則元素匹配 :invalid CSS 偽類。
    • rangeOverflow:如果值大於 max 屬性指定的最大值,則返回 true,如果小於或等於最大值,則返回 false。如果為 true,則元素匹配 :invalid:out-of-range CSS 偽類。
    • rangeUnderflow:如果值小於 min 屬性指定的最小值,則返回 true,如果大於或等於最小值,則返回 false。如果為 true,則元素匹配 :invalid:out-of-range CSS 偽類。
    • typeMismatch:如果值不符合所需的語法(當 typeemailurl 時),則返回 true,如果語法正確,則返回 false。如果為 true,則元素匹配 :invalid CSS 偽類。
    • valid:如果元素滿足所有驗證約束,因此被認為有效,則返回 true,如果失敗任何約束,則返回 false。如果為 true,則元素匹配 :valid CSS 偽類;否則匹配 :invalid CSS 偽類。
    • valueMissing:如果元素具有 required 屬性但沒有值,則返回 true,否則返回 false。如果為 true,則元素匹配 :invalid CSS 偽類。
  • willValidate:如果提交表單時將驗證元素,則返回 true;否則返回 false

約束驗證 API 還使上述元素和 form 元素可以使用以下方法。

  • checkValidity():如果元素的值沒有有效性問題,則返回 true;否則返回 false。如果元素無效,則此方法還會在元素上觸發 invalid 事件
  • reportValidity():使用事件報告無效欄位。此方法與 onSubmit 事件處理程式中的 preventDefault() 結合使用非常有用。
  • setCustomValidity(message):向元素新增自定義錯誤訊息;如果設定了自定義錯誤訊息,則元素將被視為無效,並將顯示指定的錯誤。這使您可以使用 JavaScript 程式碼來建立除標準 HTML 驗證約束提供的約束之外的其他驗證失敗。在報告問題時,該訊息將顯示給使用者。

實現自定義錯誤訊息

正如您在前面 HTML 驗證約束示例中看到的,每次使用者嘗試提交無效表單時,瀏覽器都會顯示一條錯誤訊息。此訊息的顯示方式取決於瀏覽器。

這些自動訊息有兩個缺點

  • 無法透過 CSS 以標準方式更改其外觀。
  • 它們依賴於瀏覽器語言環境,這意味著您可能在一個語言的頁面中看到另一個語言的錯誤訊息,如下面的 Firefox 螢幕截圖所示。

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

自定義這些錯誤訊息是約束驗證 API 最常見的用例之一。讓我們一起完成一個簡單的示例,瞭解如何做到這一點。

我們將從一些簡單的 HTML 開始(您可以將其放入空白的 HTML 檔案中;如果您願意,可以使用 fruit-start.html 的新副本作為基礎)

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

並在頁面中新增以下 JavaScript 程式碼

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

email.addEventListener("input", (event) => {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an email address!");
  } else {
    email.setCustomValidity("");
  }
});

在這裡,我們儲存了對電子郵件輸入的引用,然後向其添加了一個事件監聽器,該監聽器在輸入內容中的值每次更改時都會執行包含的程式碼。

在包含的程式碼中,我們檢查電子郵件輸入的 validity.typeMismatch 屬性是否返回 true,這意味著包含的值與格式良好的電子郵件地址的模式不匹配。如果是,我們使用自定義訊息呼叫 setCustomValidity() 方法。這會使輸入無效,因此當您嘗試提交表單時,提交會失敗並顯示自定義錯誤訊息。

如果 validity.typeMismatch 屬性返回 false,我們使用空字串呼叫 setCustomValidity() 方法。這會使輸入有效,因此表單將提交。

您可以在下面嘗試一下

注意:您可以在 GitHub 上找到此示例的線上版本,網址為 custom-error-message.html(另請參閱 原始碼)。

更詳細的示例

現在我們已經看到了一個非常簡單的示例,讓我們看看如何使用此 API 來構建一些稍微複雜一點的自定義驗證。

首先,是 HTML。同樣,您可以隨時與我們一起構建它

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>

此簡單表單使用 novalidate 屬性關閉瀏覽器的自動驗證;這允許我們的指令碼控制驗證。但是,這不會停用對約束驗證 API 或應用 CSS 偽類(如 :valid 等)的支援。這意味著即使瀏覽器不會在傳送資料之前自動檢查表單的有效性,您仍然可以自己執行此操作並相應地設定表單樣式。

我們要驗證的輸入是 <input type="email">,它是 required,並且具有 8 個字元的 minlength。讓我們使用自己的程式碼檢查這些內容,併為每個內容顯示自定義錯誤訊息。

我們的目標是在 <span> 元素內顯示錯誤訊息。<span> 上設定了 aria-live 屬性,以確保我們的自定義錯誤訊息會呈現給所有人,包括將其讀給螢幕閱讀器使用者。

注意:這裡的一個關鍵點是,在表單上設定 novalidate 屬性會阻止表單顯示其自己的錯誤訊息氣泡,並允許我們改為以我們自己選擇的方式在 DOM 中顯示自定義錯誤訊息。

現在,讓我們使用一些基本的 CSS 來稍微改善表單的外觀,並在輸入資料無效時提供一些視覺反饋

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

p * {
  display: block;
}

input[type="email"] {
  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 程式碼。

js
// 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.querySelector("form");
const email = document.getElementById("mail");
const emailError = document.querySelector("#mail + span.error");

email.addEventListener("input", (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.textContent = ""; // 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", (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 email 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 email 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() 以顯示相應的錯誤。
  • 每次我們嘗試提交表單時,我們都會再次檢查資料是否有效。如果是,則允許表單提交。如果不是,則執行 showError() 以顯示相應的錯誤,並使用 preventDefault() 阻止表單提交。
  • showError() 函式使用輸入的 validity 物件的各種屬性來確定錯誤是什麼,然後顯示相應的錯誤訊息。

以下是實際結果

注意:您可以在 GitHub 上找到此示例的線上版本,網址為 detailed-custom-validation.html。另請參閱 原始碼

約束驗證 API 為您提供了一個強大的工具來處理表單驗證,讓您可以對使用者介面進行極大的控制,超出您僅使用 HTML 和 CSS 可以實現的範圍。

在沒有內建 API 的情況下驗證表單

在某些情況下,例如 自定義控制元件,您將無法或不想使用約束驗證 API。您仍然可以使用 JavaScript 來驗證表單,但您需要自己編寫程式碼。

要驗證表單,請考慮以下幾個問題

我應該執行哪種型別的驗證?

您需要確定如何驗證資料:字串操作、型別轉換、正則表示式等。這取決於您。

如果表單未透過驗證,我應該怎麼做?

這顯然是一個 UI 問題。您必須決定表單的行為方式。表單是否無論如何都會發送資料?是否應該突出顯示錯誤的欄位?是否應該顯示錯誤訊息?

如何幫助使用者更正無效資料?

為了減少使用者的挫敗感,提供儘可能多的有幫助的資訊來指導他們更正輸入非常重要。您應該提供預先的建議,讓他們知道預期是什麼,以及清晰的錯誤訊息。如果您想深入研究表單驗證 UI 要求,以下是一些您應該閱讀的有用文章

一個不使用約束驗證 API 的示例

為了說明這一點,以下是前面示例的簡化版本,不使用約束驗證 API。

HTML 幾乎相同;我們只是刪除了 HTML 驗證功能。

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

同樣,CSS 不需要做太多更改;我們只是將 :invalid CSS 偽類轉換為真正的類,並避免使用屬性選擇器。

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

form {
  max-width: 200px;
}

p * {
  display: block;
}

input#mail {
  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#mail.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 程式碼,它需要做更多繁重的工作。

js
const form = document.querySelector("form");
const email = document.getElementById("mail");
const error = email.nextElementSibling;

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

// 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
window.addEventListener("load", () => {
  // 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 email address.
  const isValid = email.value.length === 0 || emailRegExp.test(email.value);
  email.className = isValid ? "valid" : "invalid";
});

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

// This defines what happens when the user tries to submit the data
form.addEventListener("submit", (event) => {
  event.preventDefault();

  const isValid = email.value.length === 0 || emailRegExp.test(email.value);
  if (!isValid) {
    email.className = "invalid";
    error.textContent = "I expect an email, darling!";
    error.className = "error active";
  } else {
    email.className = "valid";
    error.textContent = "";
    error.className = "error";
  }
});

結果如下所示

如您所見,自己構建驗證系統並不難。困難的部分是使其通用到足以跨平臺使用,並在您可能建立的任何表單上使用。有許多庫可用於執行表單驗證,例如 Validate.js

測試你的技能!

您已經讀完了本文,但您能記住最重要的資訊嗎?在繼續之前,您可以進行一些進一步的測試以驗證您是否保留了這些資訊——請參閱 測試您的技能:表單驗證

總結

如果您想要自定義樣式和錯誤訊息,客戶端表單驗證有時需要 JavaScript,但它始終要求您仔細考慮使用者。始終記住幫助您的使用者更正他們提供的資料。為此,請務必

  • 顯示明確的錯誤訊息。
  • 對輸入格式寬容。
  • 準確指出錯誤發生的位置,尤其是在大型表單中。

驗證表單填寫正確後,即可提交表單。接下來,我們將介紹 傳送表單資料

高階主題