クライアント側のフォームデータ検証

データをサーバーへ送信する前に、必須のフォームコントロールが記入され、すべてのフォームコントロールが正しい書式で記入されていることを保証することが重要です。このクライアント側フォーム検証validationは、送信されるデータが様々なフォームコントロールで指定されている要件を満たしていることを保証します。この記事では、クライアント側フォーム検証の基本概念と例を紹介します。

前提知識: コンピューターリテラシー、適度な HTMLCSSJavaScript の理解。
目的: フォーム検証とは何か、なぜ重要なのか、どのように実装するのかを理解すること。

クライアント側検証は最初のチェックであり、ユーザーの使い勝手を良くするために重要な機能ですクライアント側で不当なデータを捕捉することで、ユーザーはすぐに修正できます。無効なデータがサーバーに送られるて拒否されると、サーバーへ往復してユーザーにデータ修正するよう連絡することによってかなり時間を浪費します。

しかし、クライアント側の検証はセキュリティ対策とは考えられません!アプリは常にサーバー側でもクライアント側と同様に送信されたデータのセキュリティをチェックします。なぜならクライアント側の検証は容易に回避することができて、悪意のユーザーは簡単に、サーバーへ不正なデータを送信できます。何が起こり得るかは ウェブサイトセキュリティを見てください。サーバー側検証はこのガイドの範囲を超えますが、覚えておいてください。

フォーム検証とは何か

有名なサイトの登録フォームに行き、データを求められている書式で入力しなかいと、フィードバックがあることに気づくでしょう。次のようなメッセージが表示されます。

  • 「このフィールドは必須です」 (このフィールドが空欄にできない場合)
  • 「電話番号は XXX-XXXX の形式で入力してください」 (あるデータフォーマットが必須の場合)
  • 「有効なメールアドレスを入力してください」 (入力データが適切なフォーマットではない場合)
  • 「パスワードは 8文字から 30文字の間で、1文字以上の大文字、記号、数字を含む必要があります。」 (特に詳しいデータフォーマットが必要な場合)

これはフォーム検証と呼ばれます。データを入力したとき、ブラウザー、またはウェブアプリケーションは、そのデータが正しい書式であり、アプリケーションに設定された制約に合っているかをチェックします。ブラウザーで行われる検証はクライアント側検証と、サーバー側で行われるものはサーバー側検証と呼ばれます。この章では、クライアント側検証に集中します。

情報が正しい書式であれば、アプリケーションはデータのサーバーへの送信を許可し、(ふつうは) データベースに保存されます。正しい書式でなければ、何を修正する必要があるかを説明するメッセージを表示し、ユーザーに再入力させます。

私たちはできるだけ簡単にフォームを埋めてもらいたいわけですが、なぜフォームを検証する必要があるのでしょうか?理由は主に三つあります。

  • 正しいデータを正しい書式で入力してほしい。ユーザーのデータが誤った形式で格納されたり、ユーザーが正しい情報を入力しなかったり、省略したりすると、アプリケーションが正しく動作しないからです。
  • ユーザーのデータを保護したい。ユーザーに安全なパスワードを入力させることで、アカウント情報を保護しやすくなります。
  • 自分たちを守りたい。悪意のあるユーザーが保護のないフォームを悪用して、そのフォームを一部に持つアプリケーションに危害を加える方法がたくさんあります。(ウェブサイトセキュリティを参照してください)。

    警告: クライアントからサーバーに渡されたデータを信用しないでください。フォームが正しく検証を行い、クライアント側で悪意のある入力を防いでいるとしても、悪意のあるユーザーはネットワークリクエストを改ざんすることができます。

様々な種類のフォーム検証

ウェブで見かけるフォーム検証には二つの種類があります。

  • クライアント側検証は、データがサーバーへ送信される前にブラウザー内で行われる検証です。これはすぐに反応を返せるので、サーバー側検証よりもユーザーに親切です。これはさらに分類できます。
  • JavaScript 検証は JavaScript を使ってコーディングされるものです。これは完全にカスタマイズ可能ですが、すべて作成する(かライブラリを使う)必要があります。

内蔵フォーム検証の利用

HTML5 のフォーム制御の機能の一つに JavaScript に頼らない多くのユーザーデータの検証があります。これはフォーム要素の validation属性を使って行います。これまで多くを見てきましたが、まとめ直すと:

  • required: 属性は値が入力されているべきかどうかを指定します。
  • minlength 属性と maxlength属性: データ長の最小値と最大値を指定します。
  • min 属性と max属性: 値の最小値と最大値を指定します。
  • type 属性: その入力データが数値や、E メールアドレスや、特定の指定型かを指定します。
  • pattern 属性: データが指定された正規表現にマッチするかどうかを指定します。

入力データをこの指定されたルールに基いて検証します。検証にパスすれば妥当で検証にパスしなければ妥当ではないと考えます。

要素が妥当な場合は、次のようになります。

  • 要素が CSS の :valid 疑似クラスに一致します。これにより、妥当な要素に特定のスタイルを適用することができます。
  • ユーザーがデータを送信しようとすると、ブラウザーは止めるものが他になければ(JavaScript など)、フォームを送信します。

要素が不正なときは、次のようになります。

  • 要素が CSS の :invalid 疑似クラスに一致します。これにより、不正な要素に特定のスタイルを適用することができます。
  • ユーザーがデータを送信しようとすると、ブラウザーはフォームをブロックしてエラーメッセージを表示します。

注: サーバーへの送信を中断するエラーがいくつかあります。次のものが含まれます: badInputpatternMismatchrangeOverflow またはunderFlowstepMismatch、フォームの制御の tooLong またはtooShorttypeMismatch、と 必要とされた値のvalueMissing、また customErrorも含まれる。

入力要素の制約の検証

この節では、これまで述べてきたいくつかの属性をテストします。

簡単な最初のファイル

簡単な例から始めましょう。― 好きな果物を banana(バナナ)か cherry(サクランボ)から選べる入力欄があるとします。単純なテキストの <input> とそれに合わせたラベル、送信の <button> から成ります。ソースコードは GitHub の fruit-start.html で、ライブサンプルは次の通りです。

<form>
  <label for="choose">banana と cherry のどちらが好き?</label>
  <input id="choose" name="i_like">
  <button>Submit</button>
</form>
input:invalid {
  border: 2px dashed red;
}

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

始めるにあたって、ハードディスク内の新しいディレクトリーに fruit-start.html のコピーを作成してください。

required 属性

required 属性は、使うのがもっとも簡単な HTML5 の検証機能です。入力欄を必須にしたい場合は、この属性を使用して要素をマークすることができます。この属性が設定されていて、要素が :required にマッチすると、UI疑似クラスとフォームは送信されず、入力が空の場合のエラーメッセージが表示されるでしょう。空のままでは、この入力は不正とみなされ、:invalid 疑似クラスにマッチします。

以下のように、required 属性を入力欄に追加しましょう。

<form>
  <label for="choose">banana と cherry のどちらが好き? (要入力)</label>
  <input id="choose" name="i_like" required>
  <button>Submit</button>
</form>

このサンプルファイルの中に含まれている 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 で見ることができます (ソースコードも見てください)。

値のないままフォームを送信してみましょう。妥当ではない入力がどのようにフォーカスされるか注意しましょう。デフォルトのエラーメッセージ("Please fill out this field") が表示され、フォームの送信を阻止します。

required 属性をサポートしている要素にこの属性がある場合、その要素に値があるかないかによって、要素が :required 疑似クラスに一致するかどうかが決まります。もし <input> に値がなければ、input:invalid 疑似クラスに一致します。

注意: よりよいユーザーエクスペリエンスのために、フォームのフィールドが必要なときにはユーザーに通知しましょう。これはユーザーエクスペリエンスだけに良いというわけではなく、WCAG アクセシビリティ ガイドラインで求められています。また、あなたが本当に必要とする場合にのみ必須にしましょう。例えばあなたは誰かの性別や肩書などの情報は本当に必要でしょうか?

正規表現での検証

もう一つとてもよく使われる機能は pattern 属性で、値として正規表現を取ります。正規表現 (regex) はテキスト文字列の中の文字の組み合わせに一致させるために使うことができ、フォームの検証には理想的です (JavaScript と同様に様々な利用ができます) 。

正規表現はかなり複雑です。このモジュールでは正規表現のすべてを説明する意図はありません。いくつかの例を挙げますのでどのように動作するか基本的なアイディアを把握してください。

  • aa の 1文字に一致する (baa などではない)。
  • abca と、その次の b と、その次の c の並びに一致する。
  • ab?ca と、その次にひとつだけ b があるかないかと、その次の c の並びに一致する ( ac または abc)
  • ab*ca と、その次に任意の数の b が続き、その次に c のある並びに一致する。( ac , abc, abbbbbc, 等)
  • a|b — 一文字の a または b に一致する
  • abc|xyz —  abc の並びまたは xyz の並びに一致する。これは abcxyzay などには一致しない。

正規表現には多くの組合せがあるので例はここまでとする。完全な一覧や多くの例は、正規表現ドキュメントを参照してください。

使用例を実装しましょう。HTML を更新して pattern 属性を追加しましょう:

<form>
  <label for="choose">banana と cherry のどちらが好き?</label>
  <input id="choose" name="i_like" required pattern="[Bb]anana|[Cc]herry">
  <button>Submit</button>
</form>
input:invalid {
  border: 2px dashed red;
}

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

これは下記の更新があります。次のものを使ってみてください:

: GitHub の fruit-pattern.html でライブサンプルを見ることができます(ソースコードも見てください)

この例では、<input> 要素は "banana"、"Banana"、"cherry" または "Cherry" という 4 つの文字列値のうち 1 つを受け付けます。正規表現は大文字小文字を区別しますが、中括弧にはさまれた"Aa"のパターンを使って小文字と同様に先頭が大文字のバージョンをサポートします。

この時点で、pattern 属性の中の値を以前に見たいくつかの例と同じ値に変更してみて、入力欄が有効になるように入力する値がどのように影響するかを確認してください。自分で考えた値も書いてみて、どのようになるか確認しましょう。果物に関する値を可能にすれば、例が分かりやすくなります。

もし <input> の空ではない値が正規表現パターンに一致しなかった場合、この input:invalid 疑似クラスに一致します。

メモ: <input> 要素の型によっては、検証のために pattern 属性が必要ないことがあります。例えば email 型を指定すると、入力された文字列を、妥当な形式のメールアドレスまたは、 multiple 属性がある場合はカンマで区切られたメールアドレスのリストであることを確認する正規表現で検証します。

メモ: <textarea> 要素は pattern 属性に対応していません。

入力欄の長さの制約

あなたが、<input> または <textarea> によって作成してすべてのテキストフィールドで文字数を制限したいときには minlength 属性と maxlength 属性が使用できます。フィールドが値をもっており、その文字数が minlength の値より少ないか、文字数が maxlength の値より大きい場合は、フィールドは不正です。

ブラウザーはよくテキストフィールドに期待している以上に入力させないことがあります。単に maxlength を使うよりも良いユーザーエクスペリエンスは、入力文字数のフィードバックを提供してサイズ以下でコンテンツを編集できるようにもしておくことです。例えば、Twitter の文字入力の制限があります。これは JavaScript と maxlength を使った解決策の組合せで実現できます。

入力欄の値に制約を加える

数値のフィールド (例えば <input type="number">) の場合、min 属性と max 属性によって入力に制限を加えられます。もしそのフィールドの値がこの範囲を超える場合、そのフィールドは妥当ではありません。

他の例を見てみましょう。fruit-start.html ファイルの新しいコピーを作成してください。

では、<body> 要素の中身を削除して、以下のように置き換えてください。

<form>
  <div>
    <label for="choose">banana と cherry のどちらが好き?</label>
    <input type="text" id="choose" name="i_like" required minlength="6" maxlength="6">
  </div>
  <div>
    <label for="number">どのくらい要ります?</label>
    <input type="number" id="number" name="amount" value="1" min="1" max="10">
  </div>
  <div>
    <button>Submit</button>
  </div>
</form>
  • ここで、text フィールドには minlength 属性と maxlength 属性には 6 を指定しています。これは banana「バナナ」と cherry「さくらんぼ」の文字数と同じです。
  • またここでは、number フィールドに min 属性で 1 を max 属性で 10 を指定しました。この範囲外の数字の入力は妥当ではないと表示されます。ユーザーは矢印を使ってこの範囲外の値に増減できませんが、ユーザーが数字を手入力した場合はデータは不正となります。この数字は必須ではないので、値を除去すると妥当になります。
input:invalid {
  border: 2px dashed red;
}

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

div {
  margin-bottom: 10px;
}

例をライブで実行してみましょう。

: GitHub の fruit-length.html でライブサンプルを見ることができます(ソースコードも見てください)

: <input type="number"> (及び rangedate のような他の型)は step 属性を取ることもでき、入力コントロール(数値の増加・減少ボタンなど)を使用するときに上げ下げすることができる値の刻みを設定することができます。上の例では step 属性を入れていませんので、既定値の 1 となります。つまり 3.2 のような浮動小数でも、不正になります。

サンプル全体

HTML の内蔵検証機能の使い方を示す例の全体を示します。まずは HTML から:

<form>
  <p>
    <fieldset>
      <legend>Do you have a driver's license?<abbr title="This field is mandatory" aria-label="required">*</abbr></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> 
  <p>
    <label for="n1">How old are you?</label>
    <!-- pattern 属性は number 型の入力欄を実装していないものの、pattern
         属性には対応しているブラウザー向けの代替策として動作できます。
         なお、pattern 属性に対応しているブラウザーでは、number 型の入力欄
         で使用すると暗黙に失敗します。
         ここでは代替策としての使い方のみです。-->
    <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" aria-label="required">*</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 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 は:

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 の fruit-length.html でライブサンプルを見ることができます(ソースコードも見てください)

JavaScript を使用したフォーム検証

内蔵のエラーメッセージの見かけを制御したい場合や、HTML5 のフォーム検証に対応していないブラウザーに対処したい場合は、JavaScript を使用する必要があります。この節では、このようにするさまざまな方法を見ていきます。

HTML5 の制約検証 API

多くのブラウザーが 制約検証API  に対応しています。この API は各フォーム要素で使用できる一連のメソッドやプロパティで構成されています。

制約検証 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-rangeCSS 擬似クラスにマッチする。
    • rangeUnderflow: min 属性で指定し最小値より値が小さい場合true を、同じ大きさ以上の場合 false を返す。true なら、要素は :invalid:out-of-rangeCSS 擬似クラスにマッチする。
    • 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 には、上記の要素で利用できる、次のメソッドがあります。

  • checkValidity(): 要素の値で妥当性の問題がない場合に true を返します。そうでない場合は false を返します。要素が不正である場合、このメソッドは要素で {invalid イベントを発生させます。
  • setCustomValidity(message): 要素に独自のエラーメッセージを追加します。独自のエラーメッセージを設定すると、要素が不正であるとみなされる場合に指定したエラーが表示されます。これにより JavaScript で、標準の HTML5 制約検証 API で提供されるもの以外の検証不合格状態を作り出すことができます。ユーザーに問題を報告する際に、メッセージが表示されます。

独自のエラーメッセージを実装する

上記の HTML5 の検証制約の例で見てきたように、ユーザーが不正なフォームを送信しようとするたびにブラウザーはエラーメッセージを表示します。このメッセージを表示する方法は、ブラウザーにより異なります。

これらの自動のメッセージには、2 つの欠点があります。

  •     CSS でメッセージの表示方法を変更するための標準的な方法がありません。
  •     メッセージはブラウザーのロケールに依存しており、ある言語のページでエラーメッセージが別の言語で表示されることがあり、これは下記の Firefox スクリーンショットで見ることができます。

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

これらのメッセージの外見やテキストを変更するには、制約検証 API の最も一般的なユースケースです。この使用法を例で詳しく見てみましょう。

いくつかの簡単な HTML で開始します (空の HTML ファイルにこれを入力します; もしよければ、fruit-start.html を基礎としてもいいでしょう):

<form>
  <label for="mail">私にメールアドレスを教えてください:</label>
  <input type="email" id="mail" name="mail">
  <button>Submit</button>
</form>

このページに次の JavaScript を追加します:

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

email.addEventListener("input", function (event) {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an e-mail address!");
  } else {
    email.setCustomValidity("");
  }
});

ここでメールアドレス入力への参照を保管して、入力値が変更されるたびに制約コードが走るためのイベントリスナーを追加します。

制約コードの中で、メールアドレス入力の validity.typeMismatch プロパティが trueかどうか、つまり値がメールアドレスの形式のパターンにマッチしていないかを確認します。その場合、カスタムメッセージとともに setCustomValidity() を呼び出して、フォームを送信するときに、送信が失敗してカスタムエラーメッセージが表示されます。

validity.typeMismatchfalseの場合、空文字で setCustomValidity() メソッドを呼び出します。これは入力が妥当となり、フォームが送信されます。

次のもので試すことができます:

: この例は GitHub の custom-error-message.html で見ることができます (ソースコードも見てください)。

制約検証 API の使用例

これまでほんとうに簡単な例を見てきましたので、少し複雑な独自の検証を作成するために API を使用する方法を見ていきましょう。

始めに、HTML です。また、次のものに沿ってみてください:

<form novalidate>
  <p>
    <label for="mail">
      <span>メールアドレスを入力してください。</span>
      <input type="email" id="mail" name="mail">
      <span class="error" aria-live="polite"></span>
    </label>
  </p>
  <button>Submit</button>
</form>

この簡単なフォームでは、ブラウザーの自動検証を無効にするために novalidate 属性を使用しています。これで、検証を制御するためにスクリプトを使用できます。ただし、これは制約検証 API の対応や CSS の疑似クラス :valid, :invalid, :in-range, :out-of-range の適用を無効にするわけではありません。つまり、データを送信する前にブラウザーが自動的なフォームの妥当性確認を行わないとしても、あなた自身で確認を行って、フォームの状態に応じたスタイル設定ができます。

検証する入力は <input type="email">で、これは required(入力必須)で、8文字の minlength があります。これをわれわれのコードで確認して、それぞれカスタムエラーメッセージを表示させてみましょう。

<span>要素の中にエラーメッセージを表示させようとしています。 <span>にセットされた aria-live 属性は、スクリーンリーダーのような支援技術を使用している人々を含む皆に、独自のエラーメッセージを提示するようにします。

: ここでの要点は、フォームに novalidate 属性をつけると、フォームがエラーメッセージのバブルを表示するのを停止して、その代わりDOM内にカスタムエラーメッセージを選択した方法で表示させられることです。

この CSS はフォームの見栄えを少し良くして、入力データが無効なときの見た目のフィードバックを提供します。

/* これはサンプルの見栄えをよくするスタイルです */
body {
  font: 1em sans-serif;
  width: 200px;
  padding: 0;
  margin : 0;
}

p * {
  display: block;
}

input[type=email]{
  -webkit-appearance: none;
  appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  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;
 
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}

以下の JavaScript コードは独自のエラー検証を制御します。

// DOM ノードの選択法はたくさんあります。ここではフォーム自体、メールアドレス
// 入力ボックス、そしてエラーメッセージを配置する span 要素を取得しています。
const form  = document.getElementsByTagName('form')[0];

const email = document.getElementById('mail');
const error = document.querySelector('#mail + span.error');

email.addEventListener("input", function (event) {
  // ユーザーが何かを入力するたびに、メールアドレスのフィールドが妥当かを
  // 確認します。
  if (email.validity.valid) {
    // エラーメッセージを表示している場合に、フィールドが妥当になれば
    // エラーメッセージを取り除きます。
    error.innerHTML = ""; // メッセージの内容物をリセットします
    error.className = "error"; // メッセージの表示状態をリセットします
  } else {
    // If there is still an error, show the correct error
    showError();
  }
});

form.addEventListener("submit", function (event) {
  // ユーザーがデータを送信しようとするたびに、メールアドレスの
  // フィールドが妥当かをチェックします。
  if (!email.validity.valid) {
    
    // フィールドが妥当ではない場合、独自のエラーメッセージを
    // 表示します。
    showError();
    // また、イベントをキャンセルしてフォームの送信を止めます。
    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 e-mail 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 e-mail 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 を使用しないフォーム検証

古いブラウザーやカスタムコントロールにおいて、制約検証 API を使用できない (または使用したくない)ことがあるでしょう。このような場合でも、フォームを検証するために JavaScript が使用できます。フォームを検証には、実際のデータの検証よりもユーザーインターフェイスの疑問が多くなります。

フォームを検証するために、あなたはいくつかの疑問を考えなければなりません。

どのような検証を実施するべきか
どのようにデータを検証するかを決めなければなりません。文字列演算、型変換、正規表現など。これはあなた次第です。フォームのデータは常にテキストであり、スクリプトには常に文字列として渡されることを忘れないようにしてください。
フォームが妥当でない場合に何をするべきか
これは明らかにユーザーインターフェイスの問題です。フォームがどのように動作するかを決めなければなりません。どのような場合でもフォームのデータを送信しますか?エラー状態の入力欄を強調しますか?エラーメッセージを表示しますか?
ユーザーが不正なデータを修正することをどのように支援できるか
ユーザーの不満を軽減するためには、ユーザーに入力内容の修正を案内するために、できるだけ多くの役立つ情報を提供することがとても重要です。明確なエラーメッセージはもちろん、ユーザーが何を求められているか理解できるように前向きの提案をするべきです。フォーム検証のユーザーインターフェイスの要件について深く知りたいのであれば、ぜひ読むべきである有用な記事があります(英語)。

制約検証 API を使用しない例

これまでのことを説明するため、古いブラウザーでも動作するように前出のサンプルを作り直してみましょう。

ご覧の通り、HTML はほとんど同じであり、HTML5 の validation機能を取り除いただけです

<form>
  <p>
    <label for="mail">
        <span>メールアドレスを入力してください。</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>

同様に、CSS も大きく変更する必要はありません。:invalid 疑似クラスから実クラスへの変更と、Internet Explorer 6 で動作しない属性セレクターの使用を避けただけです。

/* これはサンプルの見栄えをよくするスタイルです */
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%;

  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;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}

JavaScript コードでは大きな変更があり、多くの面倒な作業が必要です。

// 古いブラウザーで DOM ノードを選択する方法は少ない
const form  = document.getElementsByTagName('form')[0];
const email = document.getElementById('mail');

// 以下は DOM 内で次の兄弟要素にたどり着くための技です。
// これは容易に無限ループになることがあるため、危険です。
// 新しいブラウザーでは、element.nextElementSibling を使用しましょう。
let error = email;
while ((error = error.nextSibling).nodeType != 1);

// HTML5 仕様書より
const emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

// 多くの古いブラウザーは addEventListener メソッドをサポートしていません。
// 以下はこれを扱うためのシンプルな方法です。なお唯一の方法ではありません。
function addEvent(element, event, callback) {
  let previousEventCallBack = element["on"+event];
  element["on"+event] = function (e) {
    const output = callback(e);

    // `false` を返すコールバックはコールバックチェーンを止めて、
    // イベントコールバックの実行を中断します。
    if (output === false) return false;

    if (typeof previousEventCallBack === 'function') {
      output = previousEventCallBack(e);
      if(output === false) return false;
    }
  }
};

// ここから検証制約の再構築ができます。
// CSS の疑似クラスに頼ることはできないため、メールアドレスフィールドで  
// valid/invalid クラスを明示的に設定しなければなりません。
addEvent(window, "load", function () {
  // ここで、フィールドが空かを確認しています (フィールドは必須入力ではありません)
  // 空でなければ、内容部が適切な電子メールアドレスかを確認します。
  const test = email.value.length === 0 || emailRegExp.test(email.value);

  email.className = test ? "valid" : "invalid";
});

// ユーザーがフィールドに入力したときに、何をするかを定義します。
addEvent(email, "input", function () {
  const 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 () {
  const 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.reventDefault() メソッドをサポートしていません。
    return false;
  } else {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  }
});

結果は以下のようになります。

ご覧の通り、自分でで検証システムを構築するのは大変なことではありません。難しいのはクロスプラットフォームで、かつ作成するであろうあらゆるフォームで使用できる汎用的なものにすることです。フォーム検証を行うために利用できる、Validate.js のような多くのライブラリがあります。

まとめ

クライアント側のフォーム検証は、カスタムスタイル設定やエラーメッセージには複雑な JavaScript を必要としませんが、ユーザーについては注意深く考えることが必要です。ユーザーが正しいデータを入力できるよう支援することを、常に忘れないでください。最後に、以下のことを必ず行ってください。

  • 明確なエラーメッセージを表示してください。
  • 入力形式については寛容になってください。
  • どこでエラーが発生しているかを正確に示してください(特に大きなフォームで)。

フォームが正しく埋められたことをチェックしたら、送信することができます。次のデータ送信でカバーします。

このモジュール

上級トピック