約束驗證
建立 Web 表單一直是一項複雜的任務。雖然標記表單本身很容易,但檢查每個欄位是否具有有效且一致的值更難,告知使用者問題可能會讓人頭疼。 HTML5 為表單引入了新的機制:它為 <input> 元素添加了新的語義型別以及約束驗證,以簡化在客戶端檢查表單內容的工作。基本且常見的約束可以在沒有 JavaScript 的情況下進行檢查,方法是設定新屬性;更復雜的約束可以使用約束驗證 API 進行測試。
有關這些概念的基本介紹,以及示例,請參閱 表單驗證教程。
注意:HTML 約束驗證不會消除對伺服器端驗證的需求。即使預期收到無效表單請求的數量要少得多,但仍然可以透過多種方式傳送無效請求
- 透過瀏覽器開發者工具修改 HTML。
- 透過手工製作 HTTP 請求,而不用表單。
- 透過程式設計方式將內容寫入表單(某些約束驗證*僅對*使用者輸入執行,而不會在使用 JavaScript 設定表單欄位的值時執行)。
因此,您應該始終在伺服器端驗證表單資料,與客戶端的操作保持一致。
內在約束和基本約束
語義輸入型別
用於type 屬性的內在約束是
| 輸入型別 | 約束描述 | 關聯違規 |
|---|---|---|
<input type="URL"> |
該值必須是一個絕對URL,如URL 實施標準中所定義。 | TypeMismatch 約束違規 |
<input type="email"> |
該值必須是語法上有效的電子郵件地址,通常格式為username@hostname.tld,但也可以是本地的,例如username@hostname。 |
TypeMismatch 約束違規 |
對於這兩種輸入型別,如果multiple 屬性已設定,則可以設定多個值,以逗號分隔的列表形式。如果這些值中任何一個不滿足此處描述的條件,則會觸發Type mismatch 約束違規。
請注意,大多數輸入型別沒有內在約束,因為某些型別被禁止進行約束驗證,或者具有一個將不正確值轉換為正確預設值的清理演算法。
與驗證相關的屬性
除了上面描述的type 屬性外,以下屬性用於描述基本約束
| 屬性 | 支援該屬性的輸入型別 | 可能的值 | 約束描述 | 關聯違規 |
|---|---|---|---|---|
pattern |
text, search, url, tel, email, password |
一個JavaScript 正則表示式(使用global、ignoreCase 和multiline 標誌*停用*編譯) |
該值必須與模式匹配。 |
patternMismatch 約束違規 |
min |
range, number |
一個有效的數字 | 該值必須大於或等於該值。 |
rangeUnderflow 約束違規 |
date, month, week |
一個有效的日期 | |||
datetime-local, time |
一個有效的日期和時間 | |||
max |
range, number |
一個有效的數字 | 該值必須小於或等於該值 |
rangeOverflow 約束違規 |
date, month, week |
一個有效的日期 | |||
datetime-local, time |
一個有效的日期和時間 | |||
required |
text, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file;也適用於<select> 和<textarea> 元素 |
none 因為它是一個布林屬性:存在表示true,不存在表示false | 必須有一個值(如果已設定)。 |
valueMissing 約束違規 |
step |
date |
一個整天的天數 | 除非 step 設定為any 文字,否則該值必須是min 加上 step 的整數倍。 |
stepMismatch 約束違規 |
month |
一個整數的月數 | |||
week |
一個整數的週數 | |||
datetime-local, time |
一個整數的秒數 | |||
range, number |
一個整數 | |||
minlength |
text, search, url, tel, email, password;也適用於<textarea> 元素 |
一個整數長度 | 字元(程式碼點)的數量不得小於屬性的值(如果非空)。對於<textarea>,所有換行符都被規範化為單個字元(而不是 CRLF 對)。 |
tooShort 約束違規 |
maxlength |
text, search, url, tel, email, password;也適用於<textarea> 元素 |
一個整數長度 | 字元(程式碼點)的數量不得超過屬性的值。 |
tooLong 約束違規 |
約束驗證過程
約束驗證是透過約束驗證 API 在單個表單元素上或在表單級別上,在<form> 元素本身完成的。約束驗證以以下方式完成
- 透過呼叫表單關聯的 DOM 介面的
checkValidity()或reportValidity()方法(HTMLInputElement、HTMLSelectElement、HTMLButtonElement、HTMLOutputElement或HTMLTextAreaElement),這僅評估該元素上的約束,允許指令碼獲取此資訊。checkValidity()方法返回一個布林值,指示元素的值是否透過其約束。(這通常由使用者代理在確定使用哪種 CSS 偽類:valid或:invalid時完成。)相反,reportValidity()方法將任何約束失敗報告給使用者。 - 透過呼叫
HTMLFormElement介面上的checkValidity()或reportValidity()方法。 - 透過提交表單本身。
呼叫checkValidity() 被稱為*靜態地*驗證約束,而呼叫reportValidity() 或提交表單被稱為*互動式地*驗證約束。
注意
- 如果
novalidate屬性已設定在<form>元素上,則不會進行約束的互動式驗證。 - 在
HTMLFormElement介面上呼叫submit()方法不會觸發約束驗證。換句話說,即使該方法不滿足約束,也會將表單資料傳送到伺服器。改為在提交按鈕上呼叫click()方法。 minlength和maxlength約束僅對使用者提供的輸入進行檢查。即使顯式呼叫checkValidity()或reportValidity(),也不會在以程式設計方式設定值時檢查它們。
使用約束驗證 API 的複雜約束
使用 JavaScript 和約束 API,可以實現更復雜的約束,例如,組合多個欄位的約束,或涉及複雜計算的約束。
基本上,其理念是在某些表單欄位事件(如onchange)上觸發 JavaScript 以計算約束是否被違反,然後使用field.setCustomValidity() 方法設定驗證結果:空字串表示約束滿足,任何其他字串都表示出現錯誤,該字串是顯示給使用者的錯誤訊息。
組合多個欄位的約束:郵政編碼驗證
郵政編碼格式因國家/地區而異。大多數國家/地區不僅允許使用國家/地區程式碼的可選字首(例如德國的D-,法國或瑞士的F-),而且有些國家/地區的郵政編碼只有固定位數;其他國家/地區,如英國,具有更復雜的結構,允許在某些特定位置使用字母。
注意:這不是一個完整的郵政編碼驗證庫,而只是一個對關鍵概念的演示。
例如,我們將新增一個指令碼,用於檢查此簡單表單的約束驗證
<form>
<label for="ZIP">ZIP : </label>
<input type="text" id="ZIP" />
<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>
這將顯示以下表單
首先,我們編寫一個函式來檢查約束本身
function checkZIP() {
// For each country, defines the pattern that the ZIP has to follow
const constraints = {
ch: [
"^(CH-)?\\d{4}$",
"Switzerland ZIPs must have exactly 4 digits: e.g. CH-1950 or 1950",
],
fr: [
"^(F-)?\\d{5}$",
"France ZIPs must have exactly 5 digits: e.g. F-75012 or 75012",
],
de: [
"^(D-)?\\d{5}$",
"Germany ZIPs 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])$",
"Netherland ZIPs must have exactly 4 digits, followed by 2 letters except SA, SD and SS",
],
};
// Read the country id
const country = document.getElementById("Country").value;
// Get the NPA field
const ZIPField = document.getElementById("ZIP");
// Build the constraint checker
const constraint = new RegExp(constraints[country][0], "");
console.log(constraint);
// Check it!
if (constraint.test(ZIPField.value)) {
// The ZIP follows the constraint, we use the ConstraintAPI to tell it
ZIPField.setCustomValidity("");
} else {
// The ZIP doesn't follow the constraint, we use the ConstraintAPI to
// give a message about the format required for this country
ZIPField.setCustomValidity(constraints[country][1]);
}
}
然後,我們將它連結到<select> 的onchange 事件和<input> 的oninput 事件
window.onload = () => {
document.getElementById("Country").onchange = checkZIP;
document.getElementById("ZIP").oninput = checkZIP;
};
限制檔案上傳之前的大小
另一個常見的約束是限制要上傳的檔案的大小。在將檔案傳輸到伺服器之前在客戶端檢查這一點,需要組合約束驗證 API,尤其是field.setCustomValidity() 方法,以及另一個 JavaScript API,此處為檔案 API。
以下是 HTML 部分
<label for="FS">Select a file smaller than 75 kB : </label>
<input type="file" id="FS" />
這將顯示
JavaScript 讀取選定的檔案,使用File.size() 方法獲取其大小,將其與(硬編碼的)限制進行比較,並呼叫約束 API 以告知瀏覽器是否存在違規
function checkFileSize() {
const FS = document.getElementById("FS");
const files = FS.files;
// If there is (at least) one file selected
if (files.length > 0) {
if (files[0].size > 75 * 1024) {
// Check the constraint
FS.setCustomValidity("The selected file must not be larger than 75 kB");
FS.reportValidity();
return;
}
}
// No custom constraint violation
FS.setCustomValidity("");
}
最後,我們將該方法與正確的事件掛鉤
window.onload = () => {
document.getElementById("FS").onchange = checkFileSize;
};
約束驗證的視覺樣式
除了設定約束之外,Web 開發人員還希望控制向用戶顯示的訊息以及訊息的樣式。
控制元素的外觀
控制約束違規的文字
以下專案可以幫助控制約束違規的文字
- 以下元素上的
setCustomValidity(message)方法<fieldset>。注意:在 fieldset 元素上設定自定義有效性訊息不會阻止大多數瀏覽器中的表單提交。<input><output><select>- 提交按鈕(使用具有
submit型別的<button>元素或具有submit 型別的input元素建立。其他型別的按鈕不參與約束驗證)。 <textarea>
ValidityState介面描述了上面列出的元素型別的validity屬性返回的物件。它表示輸入值無效的各種方式。它們共同幫助解釋了為什麼元素的值無效(如果它無效)。