【HTMLフォーム入門】お問い合わせフォームを作って入力チェックの入口を学ぶ

章: 2章

技術タグ: DevTools JavaScript UX バリデーション フォーム

今回やること

前回までのおみくじ編では、ブラウザ側の処理だけを信用してはいけないことを学びました。

ここからは、第2章のお問い合わせフォーム編に入ります。

フォームは、Web開発の基礎がかなり詰まっている題材です。

なぜなら、フォームではユーザーが自由にデータを入力して、こちらのシステムに送ってくるからです。

ユーザーが入力する
↓
ブラウザでチェックする
↓
サーバーへ送る
↓
サーバー側でもチェックする
↓
保存・送信・表示する

今回はその入口として、HTMLでお問い合わせフォームを作り、JavaScriptで空欄チェックをします。

ただし、今回のJavaScriptチェックは最終防衛ラインではありません。

あくまで、ユーザーが入力ミスに気づきやすくするための入口です。


今回のゴール

今回のゴールは、以下です。

  • HTMLでお問い合わせフォームを作る
  • お名前・メールアドレス・お問い合わせ内容の入力欄を用意する
  • CSSで最低限見やすく整える
  • JavaScriptで送信時の空欄チェックをする
  • 空欄があればエラーメッセージを表示する
  • 問題なければ入力チェック通過メッセージを表示する

今回はまだPHPへ送信しません。

まずは、ブラウザ上で入力チェックをするところまでを体験します。


今回必要なもの

今回はHTML・CSS・JavaScriptだけで進めます。

そのため、PHPやLaragonはまだ必要ありません。

  • VS Code
  • Live Server
  • Google Chrome
  • Chrome DevTools

第2章の後半ではPHPやデータベースを使います。

しかし今回は、まずフォームの形とJavaScriptの入力チェックに集中します。


ファイル構成

今回のファイル構成は、以下です。

contact-form-app/
├─ index.html
├─ style.css
└─ script.js
ファイル役割
index.htmlフォームの画面を作る
style.cssフォームの見た目を整える
script.js送信時の空欄チェックをする

おみくじ編と同じように、HTML・CSS・JavaScriptの役割を分けて進めます。


フォームとは何か

フォームとは、ユーザーから入力を受け取るためのHTMLの仕組みです。

お問い合わせフォーム、ログインフォーム、会員登録フォーム、検索フォームなど、WebサイトやWebアプリの多くで使われています。

フォームでは、主に次のような部品を使います。

部品役割
form入力欄全体をまとめる
label入力欄の説明を書く
input1行の入力欄を作る
textarea複数行の入力欄を作る
button送信ボタンを作る

今回作るお問い合わせフォームでも、これらを使います。


index.htmlを用意する

index.html を作成し、以下の内容を書きます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>お問い合わせフォーム</title>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js" defer></script>
</head>
<body>
  <main class="contact-page">
    <h1 class="contact-page__title">お問い合わせフォーム</h1>

    <p class="contact-page__lead">
      ご質問やご相談があれば、以下のフォームから送信してください。
    </p>

    <form id="contact-form" class="contact-form" action="#" method="post" novalidate>
      <div class="contact-form__field">
        <label class="contact-form__label" for="name">お名前</label>
        <input class="contact-form__control" type="text" id="name" name="name" />
        <p id="name-error" class="contact-form__error" aria-live="polite"></p>
      </div>

      <div class="contact-form__field">
        <label class="contact-form__label" for="email">メールアドレス</label>
        <input class="contact-form__control" type="email" id="email" name="email" />
        <p id="email-error" class="contact-form__error" aria-live="polite"></p>
      </div>

      <div class="contact-form__field">
        <label class="contact-form__label" for="message">お問い合わせ内容</label>
        <textarea class="contact-form__control contact-form__textarea" id="message" name="message"></textarea>
        <p id="message-error" class="contact-form__error" aria-live="polite"></p>
      </div>

      <button class="contact-form__button" type="submit">
        送信する
      </button>

      <p id="form-message" class="contact-form__message" aria-live="polite"></p>
    </form>
  </main>
</body>
</html>

今回のHTMLでは、form の中に3つの入力欄を用意しています。

  • お名前
  • メールアドレス
  • お問い合わせ内容

また、それぞれの入力欄の下に、エラーメッセージを表示するための p タグも用意しています。

name-error
email-error
message-error

JavaScriptでは、このエラー表示エリアにメッセージを入れていきます。


novalidateを付けている理由

今回の form には、novalidate を付けています。

<form id="contact-form" class="contact-form" action="#" method="post" novalidate>

novalidate は、ブラウザ標準の入力チェックを無効にする指定です。

普通はブラウザ標準のチェックも便利ですが、今回はJavaScriptで空欄チェックの仕組みを学びたいので、あえて無効化しています。

これにより、JavaScriptで自分たちが書いたチェック処理の動きがわかりやすくなります。


style.cssを用意する

次にCSSです。

今回の主役はフォームの入力チェックなので、見た目は作り込みすぎません。

ただし、色や余白を毎回バラバラの数値で書くと、あとから変更する時に面倒になります。

そこで今回は、簡易的なデザイントークンを用意します。

デザイントークンとは、色・余白・角丸などの値に名前を付けて管理する考え方です。

たとえば、毎回 #222222 や 24px と直接書く代わりに、--color-text や --space-l のような名前で扱います。

これは、あとから修正しやすくするための怠惰です。

同じ値を何度も探して直すのではなく、最初に名前を付けておき、変更箇所を減らします。

この連載では、ただ動くコードではなく、あとから見返して直しやすいコードも少しずつ意識していきます。

style.css に以下を書きます。

:root {
  --color-bg: #f5f5f5;
  --color-text: #222222;
  --color-surface: #ffffff;
  --color-border: #cccccc;
  --color-error: #c0392b;

  --space-s: 8px;
  --space-m: 16px;
  --space-l: 24px;
  --space-xl: 32px;
  --space-2xl: 64px;

  --radius-m: 8px;
  --radius-l: 16px;
  --radius-pill: 999px;

  --font-base: sans-serif;
}

body {
  margin: 0;
  font-family: var(--font-base);
  background-color: var(--color-bg);
  color: var(--color-text);
}

.contact-page {
  max-width: 640px;
  margin-inline: auto;
  padding-block: var(--space-2xl);
  padding-inline: var(--space-l);
}

.contact-page__title {
  margin-block-start: 0;
  margin-block-end: var(--space-m);
  font-size: 32px;
  text-align: center;
}

.contact-page__lead {
  margin-block-start: 0;
  margin-block-end: var(--space-xl);
  line-height: 1.8;
  text-align: center;
}

.contact-form {
  display: grid;
  gap: var(--space-l);
  padding-block: var(--space-xl);
  padding-inline: var(--space-l);
  border-radius: var(--radius-l);
  background-color: var(--color-surface);
}

.contact-form__field {
  display: grid;
  gap: var(--space-s);
}

.contact-form__label {
  font-weight: bold;
}

.contact-form__control {
  width: 100%;
  padding-block: 12px;
  padding-inline: 12px;
  border-width: 1px;
  border-style: solid;
  border-color: var(--color-border);
  border-radius: var(--radius-m);
  font-size: 16px;
  box-sizing: border-box;
}

.contact-form__textarea {
  min-height: 160px;
  resize: vertical;
}

.contact-form__error {
  min-height: 1.5em;
  margin-block-start: 0;
  margin-block-end: 0;
  color: var(--color-error);
  font-size: 14px;
}

.contact-form__button {
  padding-block: 14px;
  padding-inline: var(--space-l);
  border-width: 0;
  border-radius: var(--radius-pill);
  background-color: var(--color-text);
  color: var(--color-surface);
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
}

.contact-form__button:hover {
  opacity: 0.8;
}

.contact-form__message {
  margin-block-start: 0;
  margin-block-end: 0;
  font-weight: bold;
  text-align: center;
}

今回のCSSで大事なのは、エラーメッセージを見やすくすることです。

.contact-form__error {
  color: #c0392b;
}

フォームでは、ユーザーがどこを直せばいいのか分かることが重要です。


JavaScriptでフォームを取得する

ここからJavaScriptです。

まず、HTML上のフォームや入力欄、エラー表示エリアを取得します。

script.js の前半では、次のような要素を取得します。

  • フォーム本体
  • お名前の入力欄
  • メールアドレスの入力欄
  • お問い合わせ内容の入力欄
  • 各エラーメッセージ表示エリア
  • フォーム全体のメッセージ表示エリア

HTMLに付けた id を使って、JavaScriptから要素をつかまえます。

document.getElementById('contact-form');
document.getElementById('name');
document.getElementById('email');
document.getElementById('message');

送信時にJavaScriptを動かす

フォームには submit というイベントがあります。

これは、送信ボタンを押した時に発生するイベントです。

今回のコードでは、以下のように送信時の処理を登録します。

contactForm.addEventListener('submit', handleContactFormSubmit);

これにより、送信ボタンを押した時に handleContactFormSubmit() が実行されます。


event.preventDefault()で送信を止める

フォームは通常、送信ボタンを押すとページ遷移しようとします。

しかし今回は、まだPHPへ送信しません。

まずはJavaScriptで入力チェックをしたいので、通常の送信を止めます。

event.preventDefault();

event.preventDefault() は、ブラウザの標準動作を止めるための命令です。

今回の場合は、フォームの通常送信を一旦止めて、JavaScriptのチェック処理を先に動かします。


空欄チェックの考え方

今回の空欄チェックでは、入力値が空かどうかを確認します。

考え方はシンプルです。

入力値を取得する
↓
前後の空白を取り除く
↓
空文字ならエラーにする

前後の空白を取り除くために、trim() を使います。

nameInput.value.trim() === ''

これにより、スペースだけ入力された場合も空欄として扱えます。


script.jsを用意する

script.js に以下を書きます。

const contactForm = document.getElementById('contact-form');

const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const messageInput = document.getElementById('message');

const nameError = document.getElementById('name-error');
const emailError = document.getElementById('email-error');
const messageError = document.getElementById('message-error');
const formMessage = document.getElementById('form-message');

/**
 * エラーメッセージをすべてリセットする
 *
 * @returns {void}
 */
function resetErrors() {
  nameError.textContent = '';
  emailError.textContent = '';
  messageError.textContent = '';
  formMessage.textContent = '';
}

/**
 * お問い合わせフォームの入力内容をチェックする
 *
 * @returns {boolean} 入力内容が問題なければtrue、不足があればfalse
 */
function validateContactForm() {
  let isValid = true;

  if (nameInput.value.trim() === '') {
    nameError.textContent = 'お名前を入力してください。';
    isValid = false;
  }

  if (emailInput.value.trim() === '') {
    emailError.textContent = 'メールアドレスを入力してください。';
    isValid = false;
  }

  if (messageInput.value.trim() === '') {
    messageError.textContent = 'お問い合わせ内容を入力してください。';
    isValid = false;
  }

  return isValid;
}

/**
 * フォーム送信時の処理
 *
 * @param {SubmitEvent} event 送信イベント
 * @returns {void}
 */
function handleContactFormSubmit(event) {
  event.preventDefault();

  resetErrors();

  const isValid = validateContactForm();

  if (!isValid) {
    formMessage.textContent = '入力内容を確認してください。';
    return;
  }

  formMessage.textContent = '入力チェックを通過しました。';
}

contactForm.addEventListener('submit', handleContactFormSubmit);

このコードでは、まずエラーメッセージをリセットし、そのあと入力内容をチェックしています。

空欄があれば、該当する入力欄の下にエラーメッセージを表示します。

問題がなければ、フォーム下部に「入力チェックを通過しました。」と表示します。


LG流:チェック処理は関数に分ける

今回のJavaScriptでは、処理をいくつかの関数に分けています。

関数役割
resetErrors()エラーメッセージを消す
validateContactForm()入力内容をチェックする
handleContactFormSubmit()フォーム送信時の全体処理を担当する

全部を1つの場所に書くこともできます。

しかし、処理の役割を分けた方が、あとから読みやすくなります。

フォーム処理は今後、PHP送信やDB保存、fetch送信などに発展していきます。

そのため、最初から少しだけ責務を分けて書いておくと、あとで育てやすくなります。


実際に動かしてみる

Live Serverで index.html を開きます。

何も入力せずに「送信する」ボタンを押してみてください。

以下のようにエラーが表示されれば成功です。

  • お名前を入力してください。
  • メールアドレスを入力してください。
  • お問い合わせ内容を入力してください。
  • 入力内容を確認してください。

次に、すべての入力欄に文字を入れて送信してみます。

フォーム下部に「入力チェックを通過しました。」と表示されればOKです。


今回の入力チェックは防御ではなく補助

ここで大事な注意があります。

今回のJavaScriptによる空欄チェックは、ユーザーの入力ミスを減らすための補助です。

これだけでシステムを守れるわけではありません。

なぜなら、JavaScriptはブラウザ側で動くからです。

JavaScriptはユーザー側で見える
JavaScriptは無効化される可能性がある
DevToolsで書き換えられる可能性がある

つまり、JavaScriptの入力チェックは便利ですが、最終防衛ラインではありません。

本当に大事なチェックは、次回以降にPHP側でも行います。


今回わかったこと

今回は、HTMLでお問い合わせフォームを作り、JavaScriptで空欄チェックをしました。

学んだことは以下です。

  • formで入力欄をまとめられる
  • labelで入力欄の説明を書ける
  • inputで1行入力欄を作れる
  • textareaで複数行入力欄を作れる
  • submitイベントで送信時の処理を動かせる
  • event.preventDefault()で通常送信を止められる
  • trim()で前後の空白を取り除ける
  • JavaScriptでエラーメッセージを表示できる
  • フロント側の入力チェックはユーザー補助であり、最終防衛ラインではない

フォーム処理は、Web開発の基礎がかなり詰まっています。

ここから、少しずつサーバー側の処理へ進んでいきます。


次回予告

次回は、今回作ったJavaScriptの空欄チェックをあえて突破してみます。

DevToolsを使ったり、ブラウザ側の処理を避けたりしながら、フロント側バリデーションだけでは守れないことを体験します。

次回のテーマは、以下です。

【JavaScriptバリデーションの罠】フロント側の入力チェックを突破してみる

フォームは、ユーザーが自由にデータを送ってくる入口です。

だからこそ、ブラウザ側だけではなく、サーバー側でも必ずチェックする必要があります。

次回は、その理由を実際に体験します。