【jQuery】フォームにAjaxで返ってきたエラーメッセージを表示する方法

jQueryを使ってAjaxリクエストを投げて、フォームにエラーメッセージを表示する方法を紹介します。また、エラーが発生している入力項目(inputなど)の中の、一番上の入力項目にスクロールを合わせる方法も紹介します。

デモ

今回紹介するサンプルのデモです。「送信」ボタンをクリックすると、未入力の項目にエラーメッセージを表示します。また、一番上のエラーメッセージの場所までスクロールします。

別のタブで動作確認する場合は、こちらをクリックしてください!

サーバサイドの紹介

サーバサイドのスクリプトは、入力していない項目があった場合に以下のようなJSON形式のデータを返します。

{
  "is_success":false,
  "errors":[
    {"classname":"name","message":"名前を入力してください。"},
    {"classname":"email","message":"メールアドレスを入力してください。"},
    {"classname":"tel","message":"電話番号を入力してください。"},
    {"classname":"message","message":"お問い合わせ内容を入力してください。"}
  ]
}

入力フォームにエラーがあった場合は、is_successがfalseになって返ってきます。また、発生したエラーの内容は、errorsという配列に格納されています。

また、発生したエラーは、classnamemessageという、2つのキーを持っています。classnameで取得できる値は、inputなどの入力項目の、class属性と対応しています。messageは、エラーメッセージが入っています。

JavaScriptの作成

それでは、JavaScriptの作成に入ります。大まかな流れとしては、

  1. Ajaxリクエストを送信
  2. エラーメッセージを表示
  3. エラー項目にスクロール

という流れで実装していきます。

Ajaxリクエストを送信

formがsubmitされたときに、Ajaxリクエストを投げます。特にトリッキーなことはやっていませんが、POSTするデータの組み立て部分は簡略化しています。

POSTするデータを組み立てる部分

var data = {};  // POSTデータを定義します。
// 各要素(input[type="text"], textarea)でループします。
$('form input, form textarea').each(function() {
  // POSTデータを追加します。
  data[$(this).attr('class')] = $.trim($(this).val());
});

Ajaxリクエストを投げる部分の全体像

$(function() {
  $('form').submit(function() {
    var data = {};  // POSTデータを定義します。
    // 各要素(input[type="text"], textarea)でループします。
    $('form input, form textarea').each(function() {
      // POSTデータを追加します。
      data[$(this).attr('class')] = $.trim($(this).val());
    });

    // Ajaxリクエストを投げます。
    $.ajax({
      url: './inquiry.php',
      data: data,
      dataType: 'json',
      cache: false,
      type: 'POST',
      success: function(res) {
        if (res.is_success) {  // 入力エラーがなかった場合
          alert('THANKS!!');
        } else {  // 入力エラーがあった場合
          // ここに、エラーメッセージ表示などの処理を記述していきます。
        }
      }
    });

    return false;
  });
});

エラーメッセージを表示

次は、エラーメッセージを表示する部分を作成します。これは、上記ソースの「// ここに、エラーメッセージ表示などの処理を記述していきます。」の中に記述していきます。

$.each(res.errors, function(idx, error) {
  // エラーが発生した入力項目を取得します。
  var $elem = $('form .' + error.classname);

  // 入力項目の直前に、エラーメッセージを追加します。
  $elem.before('<p class="error_message">' + error.message +  '</p>');
});

$.each(res.errors, function(idx, error) {」では、返ってきたエラー数分ループさせています。functionsのidxは、何回目のループかを表す数字が、「0, 1, 2...」と入っています。

functionのerrorは、classnamemessageという、2つのキーを持つ連想配列です。classnameの値は「name」・「email」・「tel」・「message」といった、どの入力項目でエラーが発生したかを表しています。messageの値はエラーメッセージです。

それでは、ループの中に移ります。ループの中で使える、error.classnameは、各入力項目のクラス属性と対応させています。そのため、$('form .' + error.classname);で、エラーが発生した入力項目のオブジェクトを取得できます。

入力項目を取得したら、beforeメソッドを使って、入力項目の直前にエラーメッセージを表示しています。

エラーメッセージをクリア

今のままだと、エラーメッセージは表示されたままです。そうすると、リクエスト毎に同じエラーメッセージが繰り返し表示されてしまいます。

それを回避するために、リクエストを投げたタイミングでエラーメッセージをクリアします。これは、formがsubmitされたときの関数の最初に記述します。

$('form').submit(function() {

  // これを追加しました!!!
  $('form p.error_message').remove();  // エラーメッセージをクリアします。
  // これを追加しました!!!

  var data = {};  // POSTデータを定義します。
  ...

これで、エラーメッセージ表示周りを実装できました。

備考: エラーメッセージのタグについて

この例では単純にするため、エラーメッセージはpタグで囲んであります。実際の運用で、1つの入力項目に対して複数のエラーが発生する場合、ulタグなどで囲んだ方が望ましいと考えられます。

エラー項目にスクロール

最後に、返ってきたエラーの中で、画面の中で一番上にスクロールさせる処理を追加します。処理の流れは、

  1. スクロールさせる入力項目を特定する。
  2. スクロールさせる。

という流れになります。

スクロールさせる入力項目を特定する。

スクロールさせる入力項目を特定する処理は、エラーメッセージ表示と同じループに記述します。また、ループの前に、スクロールさせる入力項目を入れておく変数$targetを定義しておきます。

$targetを定義したら、ループの中でその$targetにエラーが発生した入力項目$elemを代入していきます。

var $target = null;  // スクロールさせるターゲットを定義します。

$.each(res.errors, function(idx, error) {
  ... ※エラーメッセージ表示部分を省略しています。

  if ($target == null || $target.offset().top > $elem.offset().top) {
    // スクロールのターゲットとなる入力項目を決定します。
    // エラーが複数存在した場合は、一番上の入力項目がターゲットになります。
    $target = $elem;
  }
});

$target.offset().topと$elem.offset().topは、$targetと$elem(ループで処理している入力項目)のページの一番上からの位置を取得しています。そのため、if文の$target.offset().top > $elem.offset().topは、$elem(ループで処理している入力項目)が$targetよりも上に位置する場合にtrueになります。そして、if文の中で$targetに、($targetよりも上にある)$elemを代入しています。

また、if ($target == null || ..の部分で、1回目のループでは必ずif文に入って$targetに$elemを代入するようにしています。

スクロールさせる。

どの入力項目に移動するかを特定したら、後はスクロールするだけです。スクロールは、入力項目を囲んでいるdivタグの位置までします。スクロールの処理は、animateを使って、アニメーションさせています。

また、ユーザが操作しやすいように、$target.focus();でフォーカスもあてています。

if ($target != null) {
  $target.focus();  // フォーカスを当てます。

  // 入力項目を囲むdivまでスクロールさせます。
  $targetDiv = $target.closest('div');
  $('body, html').animate(
      {scrollTop: $targetDiv.offset().top}, 200, 'swing');
}

ソース全体

今回紹介したサンプルの、ソースコードの全体像は以下のようになります。また、github上にも公開してります。https://github.com/yusukemurayama/blog-samples/tree/master/jquery/show_error_messages

フォーム・スタイル

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="./style.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="./base.js"></script>
  </head>
  <body>
    <h1>お問い合わせ(サンプル)</h1>
    <p>
      サンプルのお問い合わせフォームです。個人情報などの入力はご遠慮ください。
    </p>
    <form action="" method="post">
      <div class="form_field">
        <p>名前</p>
        <input class="name" type="text" />
      </div>
      <div class="form_field">
        <p>メールアドレス</p>
        <input class="email" type="email" />
      </div>
      <div class="form_field">
        <p>電話番号</p>
        <input class="tel" type="tel" />
      </div>
      <div class="form_field">
        <p>問い合わせ内容</p>
        <textarea class="message" rows="15" cols="40"></textarea>
      </div>
      <div class="button_field">
        <button type="submit">送信</button>
      </div>
    </form>
  </body>
</html>
body {
  margin: 20px 0 0 20px;
  width: 300px;
}
h1 {
  font-size: 20px;
}
form {
  margin-bottom: 40px;
}
.form_field {
  margin-bottom: 30px;
}
.form_field > p.error_message {
  color: #f00;
  font-weight: bold;
}
.button_field > button {
  width: 120px;
  height: 48px;
  font-size: 20px;
  cursor: pointer;
}

サーバサイド

<?php
// レスポンスデータを定義します。
$res = array('is_success' => false);

// 各POSTデータを取り出します。
$name = array_key_exists('name', $_POST) ? trim($_POST['name']) : '';
$email = array_key_exists('email', $_POST) ? trim($_POST['email']) : '';
$tel = array_key_exists('tel', $_POST) ? trim($_POST['tel']) : '';
$message = array_key_exists('message', $_POST) ? trim($_POST['message']) : '';

// エラーメッセージを格納する配列を定義します。
$errors = array();

if ($name === '') {
    $errors[] = array('classname' => 'name', 'message' => '名前を入力してください。');
}
if ($email === '') {
    $errors[] = array('classname' =>  'email', 'message' => 'メールアドレスを入力してください。');
}
if ($tel === '') {
    $errors[] = array('classname' => 'tel', 'message' => '電話番号を入力してください。');
}
if ($message === '') {
    $errors[] = array('classname' =>  'message', 'message'  => 'お問い合わせ内容を入力してください。');
}

if (count($errors) == 0) {
    // エラーが無い場合
    $res['is_success'] = true;
} else {
    // エラーがある場合は、レスポンスデータに追加します。
    $res['errors'] = $errors;
}

header("Content-Type: application/json; charset=utf-8");
echo json_encode($res);

JavaScript

$(function() {
  $('form').submit(function() {
    $('form p.error_message').remove();  // エラーメッセージをクリアします。

    var data = {};  // POSTデータを定義します。
    // 各要素(input[type="text"], textarea)でループします。
    $('form input, form textarea').each(function() {
      // POSTデータを追加します。
      data[$(this).attr('class')] = $.trim($(this).val());
    });

    // Ajaxリクエストを投げます。
    $.ajax({
      url: './inquiry.php',
      data: data,
      dataType: 'json',
      cache: false,
      type: 'POST',
      success: function(res) {
        if (res.is_success) {  // 入力エラーがなかった場合
          alert('THANKS!!');
        } else {  // 入力エラーがあった場合
          var $target = null;  // スクロールさせるターゲットを定義します。

          $.each(res.errors, function(idx, error) {
            // エラーが発生した入力項目を取得します。
            var $elem = $('form .' + error.classname);

            // 入力項目の直前に、エラーメッセージを追加します。
            $elem.before('<p class="error_message">' + error.message +  '</p>');

            if ($target == null || $target.offset().top > $elem.offset().top) {
              // スクロールのターゲットとなる入力項目を決定します。
              // エラーが複数存在した場合は、一番上の入力項目がターゲットになります。
              $target = $elem;
            }
          });

          if ($target != null) {
            $target.focus();  // フォーカスを当てます。

            // 入力項目を囲むdivまでスクロールさせます。
            $targetDiv = $target.closest('div');
            $('body, html').animate(
                {scrollTop: $targetDiv.offset().top}, 200, 'swing');
          }
        }
      }
    });

    return false;
  });
});

この記事が役に立った場合、シェアしていただけると励みになります!!