使用 HTML 表單驗證和約束驗證 API

建立 Web 表單一直是一項複雜的任務。雖然表單本身的標記很容易,但檢查每個欄位是否具有有效且一致的值則更加困難,並且告知使用者問題可能會成為一個令人頭疼的問題。HTML5 引入了新的表單機制:它為 <input> 元素添加了新的語義型別,並引入了約束驗證,以簡化客戶端檢查表單內容的工作。透過設定新屬性,無需 JavaScript 即可檢查基本的常見約束;可以使用約束驗證 API 測試更復雜的約束。

有關這些概念的基本介紹(包括示例),請參閱表單驗證教程

注意:HTML 約束驗證不能消除伺服器端驗證的必要性。儘管預計無效表單請求會少得多,但仍可以透過多種方式傳送無效請求

  • 透過瀏覽器的開發人員工具修改 HTML。
  • 透過手動建立 HTTP 請求而不使用表單。
  • 透過程式設計方式將內容寫入表單(某些約束驗證針對使用者輸入執行,而不是在你使用 JavaScript 設定表單欄位值時執行)。

因此,你應始終在伺服器端驗證表單資料,使其與客戶端所做的一致。

固有和基本約束

在 HTML 中,基本約束以兩種方式宣告

  • 透過為 <input> 元素的 type 屬性選擇最具語義意義的值,例如,選擇 email 型別會自動建立一個約束,用於檢查值是否為有效的電子郵件地址。
  • 透過設定與驗證相關的屬性的值,允許描述基本約束而無需 JavaScript。

語義輸入型別

type 屬性的固有約束包括

輸入型別 約束描述 關聯的違規
<input type="URL"> 該值必須是 URL 即時標準中定義的絕對 URL TypeMismatch 約束違規
<input type="email"> 該值必須是語法上有效的電子郵件地址,通常格式為 username@hostname.tld,但也可以是本地格式,例如 username@hostname TypeMismatch 約束違規

對於這兩種輸入型別,如果設定了 multiple 屬性,則可以設定多個值,作為逗號分隔列表。如果其中任何一個不滿足此處描述的條件,則會觸發 型別不匹配 約束違規。

請注意,大多數輸入型別沒有固有約束,因為有些輸入型別被排除在約束驗證之外,或者具有將不正確的值轉換為正確預設值的淨化演算法。

除了上面描述的 type 屬性外,以下屬性用於描述基本約束

屬性 支援該屬性的輸入型別 可能的值 約束描述 關聯的違規
pattern textsearchurltelemailpassword 一個 JavaScript 正則表示式globalignoreCasemultiline 標誌停用編譯) 該值必須與模式匹配。 patternMismatch 約束違規
min rangenumber 一個有效數字 該值必須大於或等於該值。 rangeUnderflow 約束違規
datemonthweek 一個有效日期
datetime-localtime 一個有效日期和時間
max rangenumber 一個有效數字 該值必須小於或等於該值 rangeOverflow 約束違規
datemonthweek 一個有效日期
datetime-localtime 一個有效日期和時間
required textsearchurltelemailpassworddatedatetime-localmonthweektimenumbercheckboxradiofile;也適用於 <select><textarea> 元素 ,因為它是一個布林屬性:它的存在表示,它的不存在表示 必須有一個值(如果設定)。 valueMissing 約束違規
step 日期 一個整數天數 除非步長設定為 any 字面量,否則該值必須是 min + 步長的整數倍。 stepMismatch 約束違規
月份 一個整數月數
一個整數週數
datetime-localtime 一個整數秒數
rangenumber 一個整數
minlength textsearchurltelemailpassword;也適用於 <textarea> 元素 一個整數長度 如果非空,則字元數(程式碼點)不得小於屬性值。對於 <textarea>,所有換行符都規範化為單個字元(而不是 CRLF 對)。 tooShort 約束違規
maxlength textsearchurltelemailpassword;也適用於 <textarea> 元素 一個整數長度 字元數(程式碼點)不得超過屬性值。 tooLong 約束違規

約束驗證過程

約束驗證透過約束驗證 API 在單個表單元素上或在表單級別(在 <form> 元素本身上)完成。約束驗證以下列方式完成

  • 透過呼叫與表單關聯的 DOM 介面(HTMLInputElementHTMLSelectElementHTMLButtonElementHTMLOutputElementHTMLTextAreaElement)的 checkValidity()reportValidity() 方法,該方法僅評估此元素上的約束,允許指令碼獲取此資訊。checkValidity() 方法返回一個布林值,指示元素的值是否透過其約束。(這通常由使用者代理在確定適用哪個 CSS 偽類 :valid:invalid 時完成。)相反,reportValidity() 方法向用戶報告任何約束失敗。
  • 透過呼叫 HTMLFormElement 介面上的 checkValidity()reportValidity() 方法。
  • 透過提交表單本身。

呼叫 checkValidity() 稱為靜態驗證約束,而呼叫 reportValidity() 或提交表單稱為互動式驗證約束。

備註

  • 如果 <form> 元素上設定了 novalidate 屬性,則不會發生約束的互動式驗證。
  • 呼叫 HTMLFormElement 介面上的 submit() 方法不會觸發約束驗證。換句話說,即使表單資料不滿足約束,此方法也會將表單資料傳送到伺服器。請改為呼叫提交按鈕上的 click() 方法。
  • minlengthmaxlength 約束僅在使用者提供的輸入上進行檢查。如果以程式設計方式設定值,即使顯式呼叫 checkValidity()reportValidity(),也不會檢查它們。

使用約束驗證 API 的複雜約束

使用 JavaScript 和約束 API,可以實現更復雜的約束,例如,組合多個欄位的約束,或涉及複雜計算的約束。

基本思想是在某些表單欄位事件(例如 onchange)上觸發 JavaScript,以計算約束是否被違反,然後使用 field.setCustomValidity() 方法設定驗證結果:空字串表示約束得到滿足,任何其他字串表示存在錯誤,並且此字串是向用戶顯示錯誤訊息。

組合多個欄位的約束:郵政編碼驗證

郵政編碼格式因國家/地區而異。許多國家/地區允許使用可選的國家/地區程式碼字首(例如德國的 D-、法國的 F- 和瑞士的 CH-)。一些國家/地區在郵政編碼中僅使用固定數量的數字,而其他國家/地區(例如英國)則具有更復雜的格式,允許在某些特定位置使用字母。

注意:這不是一個全面的郵政編碼驗證庫,而是對關鍵概念的演示。

作為示例,我們將新增一個檢查表單約束驗證的指令碼

html
<form>
  <label for="postal-code">Postal Code: </label>
  <input type="text" id="postal-code" />
  <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
const countrySelect = document.getElementById("country");
const postalCodeField = document.getElementById("postal-code");

function checkPostalCode() {
  // For each country, defines the pattern that the postal code has to follow
  const constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Swiss postal codes must have exactly 4 digits: e.g. CH-1950 or 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "French postal codes must have exactly 5 digits: e.g. F-75012 or 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "German postal codes 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])$",
      "Dutch postal codes must have exactly 4 digits, followed by 2 letters except SA, SD and SS",
    ],
  };

  // Read the country id
  const country = countrySelect.value;

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

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

然後我們將其連結到 <select>change 事件和 <input>input 事件

js
countrySelect.addEventListener("change", checkPostalCode);
postalCodeField.addEventListener("input", checkPostalCode);

限制檔案上傳前的大小

另一個常見的約束是限制要上傳的檔案的大小。在檔案傳輸到伺服器之前在客戶端進行檢查需要結合約束驗證 API,特別是 field.setCustomValidity() 方法,以及另一個 JavaScript API,此處是 File API。

這是 HTML 部分

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

這會顯示

JavaScript 讀取選定的檔案,使用 File.size() 方法獲取其大小,將其與(硬編碼的)限制進行比較,並呼叫約束 API 以告知瀏覽器是否存在違規

js
const fs = document.getElementById("fs");

function checkFileSize() {
  const files = fs.files;

  // If there is (at least) one file selected
  if (files.length > 0) {
    if (files[0].size > 75 * 1000) {
      // 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
fs.addEventListener("change", 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> 元素或型別為 submitinput 元素建立)。其他型別的按鈕不參與約束驗證。
    • <textarea>
  • ValidityState 介面描述了上述元素型別的 validity 屬性返回的物件。它表示輸入值無效的各種方式。它們共同幫助解釋了為什麼元素的值無法驗證(如果它無效)。