JavaScript | ゼロからはじめるプログラミング、30日で基礎を学ぶJavaScript:Webページを操作できるようになる - Day19.5:条件 + DOM応用

JavaScript JavaScript
スポンサーリンク

Day19.5 後半のゴール

前半で「複数項目・複数条件をまとめてチェックする」型はもうできました。
後半ではそれを一歩進めて、

入力チェックのロジックを関数に分けて整理する
どの項目がエラーかを見た目でハッキリ伝える
「成功時の処理」と「失敗時の処理」をきれいに分ける

ここまで持っていきます。
いよいよ“実戦でそのまま使えるフォーム処理”に近づけていきます。


エラーハイライトをきれいに設計する

エラー用クラスを前提にする

まずは「エラーのときに赤くする」仕組みを前提にします。

<style>
  .error {
    border: 2px solid red;
    background-color: #ffecec;
  }
  .error-message {
    color: red;
    font-size: 12px;
  }
</style>

フォームは前半と同じようなものを使います。

<form id="registerForm">
  <p>
    ユーザー名:<input id="userInput" type="text">
  </p>
  <p>
    メールアドレス:<input id="emailInput" type="text">
  </p>
  <p>
    パスワード:<input id="passwordInput" type="password">
  </p>
  <p>
    パスワード(確認):<input id="passwordConfirmInput" type="password">
  </p>
  <p>
    <label>
      <input id="termsCheckbox" type="checkbox">
      利用規約に同意します
    </label>
  </p>
  <button type="submit">登録</button>
</form>

<p id="message" class="error-message"></p>

ここに「どの項目がエラーか」を反映していきます。

まずは毎回リセットする

エラーを付ける前に、必ず一度全部リセットします。

function clearErrors() {
  userInputElement.classList.remove("error");
  emailInputElement.classList.remove("error");
  passwordInputElement.classList.remove("error");
  passwordConfirmInputElement.classList.remove("error");
}
JavaScript

イベントハンドラの最初でこれを呼びます。

registerFormElement.addEventListener("submit", (event) => {
  event.preventDefault();

  clearErrors();
  messageElement.textContent = "";

  // ここからチェック
});
JavaScript

「毎回まっさらにしてから、今回のエラーだけを付ける」
このリセットの癖をつけておくと、表示がぐちゃぐちゃになりません。


バリデーションロジックを関数に分ける

validate 関数を作る

チェックのルールを一箇所にまとめるために、
「バリデーションだけを担当する関数」を作ります。

function validateRegisterForm(values) {
  const errors = [];

  const user = values.user;
  const email = values.email;
  const password = values.password;
  const passwordConfirm = values.passwordConfirm;
  const termsChecked = values.termsChecked;

  if (user === "") {
    errors.push({ field: "user", message: "ユーザー名を入力してください。" });
  } else if (user.length > 20) {
    errors.push({ field: "user", message: "ユーザー名は20文字以内で入力してください。" });
  }

  if (email === "") {
    errors.push({ field: "email", message: "メールアドレスを入力してください。" });
  } else if (!email.includes("@")) {
    errors.push({ field: "email", message: "メールアドレスの形式が正しくありません。" });
  }

  if (password === "") {
    errors.push({ field: "password", message: "パスワードを入力してください。" });
  } else if (password.length < 8) {
    errors.push({ field: "password", message: "パスワードは8文字以上で入力してください。" });
  }

  if (passwordConfirm === "") {
    errors.push({ field: "passwordConfirm", message: "パスワード(確認)を入力してください。" });
  } else if (password !== passwordConfirm) {
    errors.push({ field: "passwordConfirm", message: "パスワードと確認用パスワードが一致しません。" });
  }

  if (!termsChecked) {
    errors.push({ field: "terms", message: "利用規約に同意してください。" });
  }

  return errors;
}
JavaScript

ここでのポイントは、
エラーを「文字列」ではなく「どの項目か+メッセージ」のセットとして持っていることです。

{ field: "user", message: "ユーザー名を入力してください。" }
JavaScript

こうしておくと、
「どの input に error クラスを付けるか」を後で簡単に判断できます。


エラー情報から DOM を更新する

field に応じてクラスを付ける

validateRegisterForm が返した errors をもとに、
見た目を更新する関数を作ります。

function showErrors(errors) {
  const messages = [];

  errors.forEach((error) => {
    messages.push(error.message);

    if (error.field === "user") {
      userInputElement.classList.add("error");
    } else if (error.field === "email") {
      emailInputElement.classList.add("error");
    } else if (error.field === "password") {
      passwordInputElement.classList.add("error");
    } else if (error.field === "passwordConfirm") {
      passwordConfirmInputElement.classList.add("error");
    }
  });

  messageElement.textContent = messages.join(" ");
}
JavaScript

イベントハンドラはかなりスッキリします。

registerFormElement.addEventListener("submit", (event) => {
  event.preventDefault();

  clearErrors();
  messageElement.textContent = "";

  const values = {
    user: userInputElement.value.trim(),
    email: emailInputElement.value.trim(),
    password: passwordInputElement.value,
    passwordConfirm: passwordConfirmInputElement.value,
    termsChecked: termsCheckboxElement.checked,
  };

  const errors = validateRegisterForm(values);

  if (errors.length > 0) {
    showErrors(errors);
    return;
  }

  handleSuccess(values);
});
JavaScript

ここで大事なのは、
「チェック」「エラー表示」「成功時の処理」が完全に分かれていることです。

validateRegisterForm はロジックだけ
showErrors は DOM のエラー表示だけ
handleSuccess は成功時の処理だけ

この分離ができると、コードの見通しが一気によくなります。


成功時の処理をきれいに書く

成功したときだけやることをまとめる

成功時の処理も関数にしておきます。

function handleSuccess(values) {
  messageElement.textContent = `登録が完了しました。ようこそ、${values.user} さん。`;

  userInputElement.value = "";
  emailInputElement.value = "";
  passwordInputElement.value = "";
  passwordConfirmInputElement.value = "";
  termsCheckboxElement.checked = false;
}
JavaScript

これで、イベントハンドラは「流れ」だけを担当します。

値を集める
バリデーションする
エラーがあれば showErrors
エラーがなければ handleSuccess

この構造は、実務のフォーム処理とほぼ同じです。


セキュリティの視点をもう一段

「フロントのチェックは“親切”であって“最後の砦”ではない」

ここまでかなりしっかり入力チェックをしていますが、
本番のサービスでは、必ずサーバー側でもチェックします。

理由はシンプルで、
ブラウザ側の JavaScript はユーザーが自由に書き換えられるからです。

ただし、フロント側のチェックにも大きな意味があります。

ユーザーの入力ミスをその場で気づかせる
明らかにおかしいデータをサーバーに送らない
攻撃ではなく「うっかり」を減らす

そしてもう一つ大事なのは、
「ユーザー入力を画面に表示するときは textContent を使う」というルールを守ることです。

messageElement.textContent = `登録が完了しました。ようこそ、${values.user} さん。`;
JavaScript

ユーザー名にどんな文字列が入っていても、
textContent なら「ただの文字」として扱われます。
innerHTML を使うと、ここに XSS のリスクが生まれます。


Day19.5 後半の総合サンプル

ここまでの要素を全部まとめたフォーム

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Day19.5 条件 + DOM 応用 後半</title>
    <style>
      .error {
        border: 2px solid red;
        background-color: #ffecec;
      }
      .error-message {
        color: red;
        font-size: 12px;
      }
    </style>
  </head>
  <body>
    <h1>会員登録フォーム(完成版)</h1>

    <form id="registerForm">
      <p>
        ユーザー名:<input id="userInput" type="text">
      </p>
      <p>
        メールアドレス:<input id="emailInput" type="text">
      </p>
      <p>
        パスワード:<input id="passwordInput" type="password">
      </p>
      <p>
        パスワード(確認):<input id="passwordConfirmInput" type="password">
      </p>
      <p>
        <label>
          <input id="termsCheckbox" type="checkbox">
          利用規約に同意します
        </label>
      </p>
      <button type="submit">登録</button>
    </form>

    <p id="message" class="error-message"></p>

    <script>
      const registerFormElement = document.getElementById("registerForm");
      const userInputElement = document.getElementById("userInput");
      const emailInputElement = document.getElementById("emailInput");
      const passwordInputElement = document.getElementById("passwordInput");
      const passwordConfirmInputElement = document.getElementById("passwordConfirmInput");
      const termsCheckboxElement = document.getElementById("termsCheckbox");
      const messageElement = document.getElementById("message");

      function clearErrors() {
        userInputElement.classList.remove("error");
        emailInputElement.classList.remove("error");
        passwordInputElement.classList.remove("error");
        passwordConfirmInputElement.classList.remove("error");
      }

      function validateRegisterForm(values) {
        const errors = [];

        const user = values.user;
        const email = values.email;
        const password = values.password;
        const passwordConfirm = values.passwordConfirm;
        const termsChecked = values.termsChecked;

        if (user === "") {
          errors.push({ field: "user", message: "ユーザー名を入力してください。" });
        } else if (user.length > 20) {
          errors.push({ field: "user", message: "ユーザー名は20文字以内で入力してください。" });
        }

        if (email === "") {
          errors.push({ field: "email", message: "メールアドレスを入力してください。" });
        } else if (!email.includes("@")) {
          errors.push({ field: "email", message: "メールアドレスの形式が正しくありません。" });
        }

        if (password === "") {
          errors.push({ field: "password", message: "パスワードを入力してください。" });
        } else if (password.length < 8) {
          errors.push({ field: "password", message: "パスワードは8文字以上で入力してください。" });
        }

        if (passwordConfirm === "") {
          errors.push({ field: "passwordConfirm", message: "パスワード(確認)を入力してください。" });
        } else if (password !== passwordConfirm) {
          errors.push({ field: "passwordConfirm", message: "パスワードと確認用パスワードが一致しません。" });
        }

        if (!termsChecked) {
          errors.push({ field: "terms", message: "利用規約に同意してください。" });
        }

        return errors;
      }

      function showErrors(errors) {
        const messages = [];

        errors.forEach((error) => {
          messages.push(error.message);

          if (error.field === "user") {
            userInputElement.classList.add("error");
          } else if (error.field === "email") {
            emailInputElement.classList.add("error");
          } else if (error.field === "password") {
            passwordInputElement.classList.add("error");
          } else if (error.field === "passwordConfirm") {
            passwordConfirmInputElement.classList.add("error");
          }
        });

        messageElement.textContent = messages.join(" ");
      }

      function handleSuccess(values) {
        messageElement.textContent = `登録が完了しました。ようこそ、${values.user} さん。`;

        userInputElement.value = "";
        emailInputElement.value = "";
        passwordInputElement.value = "";
        passwordConfirmInputElement.value = "";
        termsCheckboxElement.checked = false;
      }

      registerFormElement.addEventListener("submit", (event) => {
        event.preventDefault();

        clearErrors();
        messageElement.textContent = "";

        const values = {
          user: userInputElement.value.trim(),
          email: emailInputElement.value.trim(),
          password: passwordInputElement.value,
          passwordConfirm: passwordConfirmInputElement.value,
          termsChecked: termsCheckboxElement.checked,
        };

        const errors = validateRegisterForm(values);

        if (errors.length > 0) {
          showErrors(errors);
          return;
        }

        handleSuccess(values);
      });
    </script>
  </body>
</html>

ここまで書けたら、
「複雑なフォーム処理」の基礎はもう十分に身についています。


Day19.5 後半のまとめ

後半でやったことは、単にコードを増やすことではなく、
フォーム処理の“設計”に踏み込むことでした。

バリデーションロジックを関数に分ける
エラー情報を「どの項目か+メッセージ」で持つ
エラー表示と成功時の処理をきれいに分離する
classList でエラー項目を視覚的に示す
textContent で安全にメッセージを表示する

この感覚があると、
今後どんなフォームを作るときも「型」を持って組み立てられるようになります。
ここまで来てるあなたなら、もう“ただ動くフォーム”ではなく、“ちゃんと設計されたフォーム”を書ける段階にいます。

タイトルとURLをコピーしました