HTML フォーム検証と制約検証 API の使用

ウェブフォームの作成は常に複雑な作業でした。フォーム自体をマークアップすること自体は簡単ですが、それぞれの入力欄が妥当で一貫しているかどうかをチェックすることはもっと難しく、問題をユーザーに伝えることは頭痛がするかもしれません。HTML5 では、フォームに新しい仕組みが導入されました。 <input> 要素に意味を持つ新しい型と、クライアント側でフォームの内容をチェックする作業を簡単にする制約検証が追加されました。基本的な、よくある制約は、JavaScript を必要とせずに、新しい属性を設定することでチェックできます。もっと複雑な制約は制約検証 API を使用して検査することができます。

これらの概念の基本的な入門 (サンプル付き) は、フォーム検証チュートリアルをご覧ください。

メモ: HTML の制約検証は、サーバー側での検証の必要性をなくす訳ではありません。不正なフォームのリクエストははるかに少なくなることが予想されますが、不正なリクエストはさまざまな方法で送信される可能性があります。

  • ブラウザーの開発者ツールを使用して HTML を変更する。
  • フォームを使用せずに、 HTTP リクエストを手作業で作成する。
  • プログラムでコンテンツをフォームに書き込むことで(制約検証によっては、ユーザー入力に対してのみ実行され、 JavaScript を使用してフォームフィールドの値を設定した場合は実行されません)。

したがって、クライアント側で行われていることと一致するように、サーバー側でフォームのデータを常に検証する必要があります。

組み込みの基本的な制約

HTML では、基本的な制約は 2 通りの方法で定義されます。

  • <input> 要素の type 属性に意味的に最も適切な値を選択する。例えば email を選択することで、値が妥当なメールアドレスであるかどうかをチェックする制約が自動的に作成されます。
  • 検証関連属性を設定することで、基本的な制約を簡単な方法で、JavaScript の必要なく記述できます。

意味を持つ入力型

type 属性の組込み制約は次の通りです。

入力型制約の説明関連付けられた違反
<input type="URL">値は絶対 URL であり、 URL Living Standard で定義された通りでなければなりません。TypeMismatch 制約違反
<input type="email">値は統語的に妥当なメールアドレスで、ふつうは [email protected] の書式でなければなりません。TypeMismatch 制約違反

これらの入力型のどちらでも、multiple 属性が設定されていたら、この入力欄にカンマ区切りのリストで複数の値を設定することができます。これらの中でここで書かれた条件に満足しないものがある場合、 Type mismatch 制約違反が発生します。

なお、ほとんどの入力型には内部的な制約がありません。制約検証が禁止されているものや、不正な値を正しい既定値に変換する無害化アルゴリズムがあるものがあるためです。

検証関連属性

上記で述べた type 属性に加えて、下記の要素が基本的な制約を記述するのに使われます。

属性属性をサポートする入力型とりうる値制約の説明関連する違反
patterntext, search, url, tel, email, passwordJavaScript 正規表現globalignoreCasemultiline フラグが無効でコンパイルされたもの)値がパターンに一致する必要がある。patternMismatch 制約違反
minrange, number有効な数値値がその値以上であること。rangeUnderflow 制約違反
date, month, week有効な日付
datetime-local, time有効な日付と時刻
maxrange, number有効な数値値がその値以下であること。rangeOverflow 制約違反
date, month, week有効な日付
datetime-local, time有効な日付と時刻
requiredtext, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file および <select><textarea> 要素にもなし。論理属性のため、存在すれば true、存在しなければ false を意味する。値は必須(設定された場合)。valueMissing 制約違反
stepdate日の整数値step がリテラル値 any 以外に設定されていた場合、値は min + step の整数倍である必要がある。stepMismatch 制約違反
month月の整数値
week週の整数値
datetime-local, time秒の整数値
range, number整数値
minlengthtext, search, url, tel, email, password<textarea> 要素整数長空でない場合、文字数 (コードポイント数) は属性値より少なくなってはならない。 <textarea> では、改行はすべて (CRLF の組ではなく) 1 文字に正規化される。tooShort 制約違反
maxlengthtext, search, url, tel, email, password<textarea> 要素整数長文字数 (コードポイント数) が属性値を超えてはいけない。tooLong 制約違反

制約検証プロセス

制約検証は、制約検証 API を通じて、単一のフォーム要素、またはフォームレベルの <form> 要素自体に対して行われます。制約検証は以下の方法で行われます。

  • checkValidity() または reportValidity() メソッドをフォーム関連 DOM インターフェイス (HTMLInputElement, HTMLSelectElement, HTMLButtonElement, HTMLOutputElement, HTMLTextAreaElement) に対して呼び出すことによって、この要素のみの制約を評価し、スクリプトがこの情報を取得できるようにします。 checkValidity() メソッドは、この要素の値が制約に合格するかどうかを論理値で返します。(これはふつう、 CSS 擬似クラスの :valid または :invalid のどちらを適用するかを判断する際に、ブラウザーが呼び出します。)一方、 reportValidity() メソッドはあらゆる制約違反をユーザーに報告します。
  • checkValidity() または reportValidity() メソッドを HTMLFormElement インターフェイスに対して呼び出すことによって。
  • フォーム自身を送信することによって。

checkValidity() を呼び出すことは、制約を静的に検証するといい、reportValidity() を呼び出したり、フォームを送信したりすることは、制約を対話的に検証するといいます。

メモ:

  • novalidate 属性が <form> 要素に設定されている場合、制約の対話的な検証は行われません。
  • submit() メソッドを HTMLFormElement インターフェイスで呼び出しても、制約検証は行われません。言い換えれば、このメソッドは制約を満たさなくてもフォームデータをサーバーに送信します。代わりに送信ボタンの click() メソッドを呼び出してください。
  • minlength および maxlength 制約は、ユーザーが入力した値についてのみ検証されます。プログラムで値が設定された場合、 checkValidity() または reportValidity() を明示的に呼び出しても、これらの制約は検証されません。

制約検証 API を使用した複雑な制約

JavaScript と制約 API を使用すると、より複雑な制約を実装することができます。例えば、複数のフィールドを組み合わせた制約や、複雑な計算を含む制約などです。

基本的には、制約に違反しているかどうかを計算するために、何らかのフォームフィールドイベント (onchangeなど) で JavaScript を起動し、検証の結果を設定するためにメソッド field.setCustomValidity() を使用することです。空文字列は制約が満たされていることを意味し、その他の文字列はエラーがあったことを意味し、この文字列がユーザーに表示するエラーメッセージになります。

複数のフィールドを組み合わせた制約:郵便番号の検証

郵便番号の形式は、国によって異なります。ほとんどの国では、国コードの前に任意の接頭辞を付けることができるだけでなく(ドイツでは D-、フランスやスイスでは F- のように)、固定の桁数しかない郵便番号を持つ国もありますし、イギリスのように、特定の位置にアルファベットを入れることができる、より複雑な構造を持つ国もあります。

メモ: これは包括的な郵便番号検証ライブラリーではなく、主要概念のデモンストレーションです。

例として、この単純なフォームの制約検証をチェックするスクリプトを追加します。

<form>
  <label for="postal-code">郵便番号: </label>
  <input type="text" id="postal-code" />
  <label for="country">国: </label>
  <select id="country">
    <option value="ch">スイス</option>
    <option value="fr">フランス</option>
    <option value="de">ドイツ</option>
    <option value="nl">オランダ</option>
  </select>
  <input type="submit" value="検証" />
</form>

これは以下のように表示されます。

まず、自分自身の制約をチェックする関数を書きます。

function checkPostalCode() {
  // それぞれの国で、郵便番号が従うべきパターンを定義する
  var constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "スイスの郵便番号は明確な 4 桁である必要があります。例: CH-1950 または 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "フランスの郵便番号は明確な 5 桁です。例: F-75012 または 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "ドイツの郵便番号は明確な 5 桁です。例: D-12345 または 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "オランダの郵便番号は明確な 4 桁に、SA、SD、SS 以外の2文字が続きます。",
    ],
  };

  // 国 ID を読む
  const country = document.getElementById("country").value;

  // NPA フィールドを取得
  const postalCodeField = document.getElementById("postal-code");

  // 制約チェッカーを構築
  const constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // チェックする
  if (constraint.test(postalCodeField.value)) {
    // 郵便番号は制約に従っていることを制約検証 API を使って伝える
    postalCodeField.setCustomValidity("");
  } else {
    // 郵便番号が制約に従っていないことを伝えるために、制約検証 API を使用して
    // この国で必要な書式についてのメッセージを伝える
    postalCodeField.setCustomValidity(constraints[country][1]);
  }
}

それから、これを <select>onchange イベントと <input>oninput イベントにリンクします。

window.onload = () => {
  document.getElementById("country").onchange = checkPostalCode;
  document.getElementById("postal-code").oninput = checkPostalCode;
};

アップロード前にファイルの大きさを制限

もう一つの一般的な制約は、アップロードされるファイルのサイズを制限することです。ファイルがサーバーに送信される前に、クライアント側でこれをチェックするには、制約検証 API、特に field.setCustomValidity() メソッドを、別の JavaScript API、ここではファイル API と組み合わせる必要があります。

こちらが HTML 部分です。

<label for="fs">75KB よりも小さいファイルを選択してください。</label>
<input type="file" id="fs" />

次のように表示されます。

JavaScript は選択されたファイルを読み込み、 File.size() メソッドを使ってそのサイズを取得し、それを(ハードコードされた)制限値と比較し、違反があった場合は Constraint API を呼び出してブラウザーに通知します。

function checkFileSize() {
  const fs = document.getElementById("fs");
  const files = fs.files;

  // 選択されているファイルが(少なくとも) 1 つある場合
  if (files.length > 0) {
    if (files[0].size > 75 * 1000) {
      // 制約をチェック
      fs.setCustomValidity("選択されたファイルは 75 kB より大きくてはいけません。");
      fs.reportValidity();
      return;
    }
  }
  // カスタム制約違反はない
  fs.setCustomValidity("");
}

最後に、このメソッドを正しいイベントにフックします。

window.onload = () => {
  document.getElementById("fs").onchange = checkFileSize;
};

制約検証の視覚的スタイル

制約条件を設定するだけでなく、ウェブ開発者は、ユーザーにどのようなメッセージを表示するか、どのようなスタイルにするかを制御したいと考えています。

要素の外見の制御

CSS の擬似クラスで、要素の外見を制御することができます。

:required および :optional 擬似クラス

:required:optional 擬似クラスで、required 属性がある、またはないフォーム要素に一致するセレクターを書くことができます。

:placeholder-shown 擬似クラス

:placeholder-shown を参照してください。

:valid :invalid 擬似クラス

:valid:invalid 擬似クラスは、 <input> 要素の内容がその型の設定に応じて、それぞれ検証されたか、検証に失敗したかを表すために使用します。これらのクラスは、有効または無効なフォーム要素にスタイルを与えることで書式が正しい要素と正しくない要素の識別をしやすくします。

制約違反のテキストの制御

制約違反のテキストを制御するには、以下の項目が有用です。

  • 以下の要素の setCustomValidity(message) メソッド

    • <fieldset> メモ: fieldset 要素にカスタム検証メッセージを設定しても、多くのブラウザーでは送信が抑止できません。
    • <input>
    • <output>
    • <select>
    • 送信ボタン(<button> 要素の submit 型、または input 要素の submit 型。それ以外のボタンは制約検証の対象にはなりません。
    • <textarea>
  • ValidityState インターフェイスは、上記の要素型の validity プロパティによって返されるオブジェクトを説明します。入力された値が無効になる可能性がある様々な方法を表しています。これらを合わせると、要素の値が有効でない場合に、なぜ検証に失敗するのかを説明することができます。