15.

フォーム submit 前 JavaScript 呼出完全ガイド

編集
この記事の要点
  • 定番: <form onsubmit="return validate(event)"> + return false で送信中止
  • モダン: form.addEventListener("submit", e => { e.preventDefault(); ... })
  • HTML5 標準: required / pattern / type="email" + form.checkValidity()
  • 非同期チェック(重複確認等)は e.preventDefault() → fetch → 成功時 form.submit()
  • Vue / React / Yup / Joi 等のフレームワークに任せるのが最終形

submit 前に JS を呼ぶ 3 つの方法

1. onsubmit 属性で関数呼出

<form action="/register" method="post" onsubmit="return validate(event)">
    <input name="email" id="email">
    <button type="submit">登録</button>
</form>

<script>
function validate(event) {
    var email = document.getElementById('email').value;
    if (!email.includes('@')) {
        alert('メールアドレスが不正です');
        return false;   // ★ false を返すと送信されない
    }
    return true;        // true なら送信
}
</script>

2. addEventListener(推奨)

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
    const email = form.elements['email'].value;
    if (!email.includes('@')) {
        e.preventDefault();   // ★ デフォルトの送信を中止
        alert('メールアドレスが不正です');
        return;
    }
    // 何もしなければそのまま送信される
});

3. ボタン onclick で submit() 呼出

<form id="myForm" action="/register" method="post">
    <input name="email" id="email">
    <button type="button" onclick="doSubmit()">登録</button>
</form>

<script>
function doSubmit() {
    if (!validateEmail()) return;
    document.getElementById('myForm').submit();
    // ★ form.submit() は submit イベントを発火しない!
}
</script>

3 方法の比較

方法キーボード送信(Enter)イベント上書き推奨
onsubmit 属性 + return falseOK1 つだけレガシー
addEventListener('submit')OK複数可★ 推奨
button onclick + submit()NG(Enter キーで漏れる)複数可非推奨

preventDefault / return false の違い

// 旧式: return false (onsubmit 属性内で有効)
function onSubmit(e) {
    if (!valid()) return false;   // 送信中止 + イベント伝播も止める
}

// モダン: event.preventDefault()
form.addEventListener('submit', (e) => {
    if (!valid()) {
        e.preventDefault();     // 送信のみ中止
        e.stopPropagation();    // バブリングも止めるなら
    }
});

// jQuery の場合 return false は両方 = preventDefault + stopPropagation
$('form').submit(function(e) {
    if (!valid()) return false;
});

HTML5 標準バリデーション

属性だけで基本的なバリデーションが効きます:

<form>
    <!-- 必須 -->
    <input name="name" required>

    <!-- メール形式 -->
    <input name="email" type="email" required>

    <!-- 数値範囲 -->
    <input name="age" type="number" min="0" max="150" required>

    <!-- 文字数 -->
    <input name="password" minlength="8" maxlength="100" required>

    <!-- 正規表現 -->
    <input name="zip" pattern="\d{3}-\d{4}" title="例: 100-0001">

    <!-- 日付 -->
    <input name="birthday" type="date" required>

    <button>送信</button>
</form>

未入力で送信すると、ブラウザが標準のエラー表示を出して送信を防ぎます。JS は不要です。

Constraint Validation API

HTML5 のバリデーション状態を JS から細かく制御:

const form = document.querySelector('form');
const email = form.elements['email'];

form.addEventListener('submit', (e) => {
    // 全フィールド有効か
    if (!form.checkValidity()) {
        e.preventDefault();
        form.reportValidity();   // ブラウザ標準のエラー表示
        return;
    }
});

// 個別フィールドのチェック
console.log(email.validity);
// {
//   valid: false,
//   valueMissing: true,    // required で空
//   typeMismatch: false,   // type="email" 形式違反
//   patternMismatch: false,
//   tooLong: false,
//   tooShort: false,
//   rangeUnderflow: false,
//   rangeOverflow: false,
//   stepMismatch: false,
//   badInput: false,
//   customError: false,
// }

// カスタムエラーメッセージ
email.setCustomValidity('このメールは既に使われています');
email.reportValidity();

// クリア
email.setCustomValidity('');

非同期バリデーション(重複チェック等)

const form = document.querySelector('form');

form.addEventListener('submit', async (e) => {
    e.preventDefault();   // ★ 一旦止めて非同期チェック

    // HTML5 標準チェック
    if (!form.checkValidity()) {
        form.reportValidity();
        return;
    }

    // サーバ側で重複チェック
    const email = form.elements['email'].value;
    const res = await fetch('/api/check-email?email=' + encodeURIComponent(email));
    const { exists } = await res.json();

    if (exists) {
        form.elements['email'].setCustomValidity('既に登録済みです');
        form.elements['email'].reportValidity();
        return;
    }

    // OK なら手動で送信
    form.submit();
});

React のフォーム例

import { useState } from 'react';

function RegisterForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  const onSubmit = async (e) => {
    e.preventDefault();
    if (!email.includes('@')) {
      setError('メール形式が不正');
      return;
    }
    const res = await fetch('/api/register', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({ email }),
    });
    if (!res.ok) setError('登録失敗');
  };

  return (
    <form onSubmit={onSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      {error && <p>{error}</p>}
      <button>登録</button>
    </form>
  );
}

Vue 3 のフォーム例

<script setup>
import { ref } from 'vue';
const email = ref('');
const error = ref('');

async function onSubmit() {
  if (!email.value.includes('@')) {
    error.value = 'メール形式が不正';
    return;
  }
  await fetch('/api/register', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ email: email.value }),
  });
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input v-model="email">
    <p v-if="error">{{ error }}</p>
    <button>登録</button>
  </form>
</template>

スキーマバリデーション (Yup / Zod / Joi)

// Yup
import * as yup from 'yup';

const schema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().min(0).max(150).required(),
});

try {
  await schema.validate(formData);
} catch (err) {
  console.error(err.errors);
}

// Zod
import { z } from 'zod';
const Schema = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});
const result = Schema.safeParse(formData);
if (!result.success) console.error(result.error);

FAQ

Q: form.submit() で onsubmit イベントが発火しない
A: 仕様です。プログラムから form.submit() を呼ぶと submit イベントはスキップされます。バリデーション後の送信は新しく form.requestSubmit() (モダン環境) を使うと submit イベントも発火します。

Q: Enter キーで送信されてしまう
A: 仕様。submit イベントは Enter でも発火するので addEventListener('submit') なら漏れません。input の Enter を完全に止めたいなら keydowne.key === 'Enter' && e.preventDefault()

Q: 二重送信を防ぎたい
A: submit ボタンを btn.disabled = true に。あるいは Vue/React で送信中フラグを管理。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 記述方法
  2. コメント
  3. 変数の宣言
  4. 関数
  5. 演算子
  6. 条件文
  7. 配列
  8. 連想配列
  9. ループ処理
  10. 非同期処理
  11. 同期処理
  12. 確認ウィンドウを表示する方法
  13. 文字の置換
  14. base urlを取得する方法
  15. formのsubmit前にjavascriptを呼び出す方法
  16. undefinedのイコール判定
  17. Javascript のみで form を post で submit する方法

最近更新/作成されたページ