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 で安全にメッセージを表示する
この感覚があると、
今後どんなフォームを作るときも「型」を持って組み立てられるようになります。
ここまで来てるあなたなら、もう“ただ動くフォーム”ではなく、“ちゃんと設計されたフォーム”を書ける段階にいます。
