JavaScript form submitの制御方法 - 初心者向け実装ガイド

JavaScriptを使ったフォーム送信制御について初心者向けに詳しく解説。preventDefaultによる送信停止、バリデーション実装、非同期送信、エラーハンドリングまで実践的なコード例で学びます。

Learning Next 運営
39 分で読めます

JavaScript form submitの制御方法 - 初心者向け実装ガイド

みなさん、Webフォームを作っていて困ったことありませんか?

「送信前にデータをチェックしたい!」 「送信後にページがリロードされるのを防ぎたい!」 「エラーが出た時に分かりやすく表示したい!」

こんな悩みを感じたことがある方、きっと多いですよね。

実は、JavaScriptを使えばフォーム送信を自由にコントロールできるんです! この記事では、初心者の方でも分かるように、フォーム送信制御の基本から実践的なテクニックまで丁寧に解説します。

preventDefault、バリデーション、非同期送信まで。 一緒に学んで、ユーザーフレンドリーなフォームを作ってみませんか?

フォーム送信ってどうなってるの?

普通のフォーム送信の流れ

まず、普通のHTMLフォームがどう動くか見てみましょう。

<!-- 基本的なHTMLフォーム -->
<form action="/submit" method="POST">
<input type="text" name="username" placeholder="ユーザー名">
<input type="email" name="email" placeholder="メールアドレス">
<button type="submit">送信</button>
</form>

普通は「送信」ボタンを押すとこんな流れになります。

// 通常のフォーム送信の流れ
// 1. ユーザーが送信ボタンをクリック
// 2. ブラウザがformタグのaction属性のURLに送信
// 3. ページが完全にリロードされる
// 4. サーバーから新しいページが返される

でも、これだと不便なことがありますよね。

JavaScriptで制御するメリット

JavaScriptで制御すると、こんな良いことがあります。

  • 送信前にデータをチェックできる
  • ページリロードなしで送信できる
  • エラーメッセージを分かりやすく表示できる
  • 送信中の状態を表示できる

とても便利ですね!

preventDefault()で送信をコントロールしよう

preventDefault()の基本

まず、フォーム送信を止める方法から学びましょう。

// フォーム送信を止める基本的な方法
const form = document.getElementById('my-form');
form.addEventListener('submit', function(event) {
// デフォルトの送信動作を停止
event.preventDefault();
console.log('フォーム送信が止まりました!');
});

preventDefault()を呼ぶと、通常のフォーム送信が止まります。 これで、自分好みの処理を書けるようになります。

実際に使ってみよう

お問い合わせフォームの例を作ってみましょう。

<!-- HTML部分 -->
<form id="contact-form">
<div>
<label>お名前:</label>
<input type="text" name="name" required>
</div>
<div>
<label>メールアドレス:</label>
<input type="email" name="email" required>
</div>
<div>
<label>メッセージ:</label>
<textarea name="message" required></textarea>
</div>
<button type="submit">送信</button>
<div id="status"></div>
</form>

そして、JavaScriptで送信制御を追加します。

// フォーム送信制御の実装
const form = document.getElementById('contact-form');
const statusDiv = document.getElementById('status');
form.addEventListener('submit', function(event) {
// デフォルトの送信動作を停止
event.preventDefault();
// フォームデータを取得
const formData = new FormData(event.target);
const data = Object.fromEntries(formData);
console.log('送信されたデータ:', data);
// 状態表示を更新
statusDiv.textContent = '送信処理中...';
statusDiv.style.color = 'blue';
// 送信処理をシミュレート
setTimeout(() => {
statusDiv.textContent = '送信が完了しました!';
statusDiv.style.color = 'green';
// フォームをリセット
event.target.reset();
}, 2000);
});

これで、送信ボタンを押してもページがリロードされません。 代わりに、カスタムの処理が実行されます。

条件によって送信を制御

バリデーションの結果によって送信を制御してみましょう。

// 条件付きでの送信制御
form.addEventListener('submit', function(event) {
// バリデーション結果をチェック
const isValid = validateForm(event.target);
if (!isValid) {
// バリデーションエラーがある場合は送信を停止
event.preventDefault();
showError('入力内容に問題があります');
return;
}
// バリデーションが成功した場合
const useAjax = true; // Ajax送信を使うかどうか
if (useAjax) {
// Ajax送信の場合は通常の送信を停止
event.preventDefault();
submitViaAjax(event.target);
} else {
// 通常の送信を許可(preventDefaultを呼ばない)
console.log('通常のフォーム送信を実行');
}
});
function validateForm(form) {
const requiredFields = form.querySelectorAll('[required]');
return Array.from(requiredFields).every(field => field.value.trim() !== '');
}
function showError(message) {
statusDiv.textContent = message;
statusDiv.style.color = 'red';
}
function submitViaAjax(form) {
console.log('Ajax送信を実行中...');
// 実際のAjax実装はこの後で説明します
}

こうすると、バリデーションの結果に応じて送信方法を選べます。

バリデーションでデータをチェックしよう

リアルタイムバリデーション

ユーザーが入力している最中にチェックする方法です。

// リアルタイムバリデーションの実装
class FormValidator {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.setupValidation();
}
setupValidation() {
// フォーム送信時のバリデーション
this.form.addEventListener('submit', (event) => {
event.preventDefault();
this.handleSubmit(event);
});
// リアルタイムバリデーション
const inputs = this.form.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('blur', () => {
this.validateField(input);
});
input.addEventListener('input', () => {
this.clearFieldError(input);
});
});
}
validateField(field) {
let isValid = true;
let errorMessage = '';
// 必須チェック
if (field.hasAttribute('required') && !field.value.trim()) {
isValid = false;
errorMessage = 'この項目は必須です';
}
// メール形式チェック
if (field.type === 'email' && field.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(field.value)) {
isValid = false;
errorMessage = '正しいメールアドレス形式で入力してください';
}
}
if (isValid) {
this.markFieldAsValid(field);
} else {
this.markFieldAsInvalid(field, errorMessage);
}
return isValid;
}
markFieldAsInvalid(field, message) {
field.style.borderColor = 'red';
this.showFieldError(field, message);
}
markFieldAsValid(field) {
field.style.borderColor = 'green';
this.clearFieldError(field);
}
clearFieldError(field) {
field.style.borderColor = '';
const errorElement = this.getErrorElement(field);
if (errorElement) {
errorElement.remove();
}
}
showFieldError(field, message) {
this.clearFieldError(field);
const errorElement = document.createElement('div');
errorElement.style.color = 'red';
errorElement.style.fontSize = '12px';
errorElement.style.marginTop = '2px';
errorElement.textContent = message;
errorElement.setAttribute('data-field-error', field.name);
field.parentNode.appendChild(errorElement);
}
getErrorElement(field) {
return this.form.querySelector(`[data-field-error="${field.name}"]`);
}
async handleSubmit(event) {
// 全フィールドのバリデーション
const inputs = this.form.querySelectorAll('input, textarea');
const validationResults = Array.from(inputs).map(input =>
this.validateField(input)
);
const isValid = validationResults.every(result => result);
if (isValid) {
console.log('バリデーション成功!');
await this.submitForm(event.target);
} else {
console.log('バリデーションエラーがあります');
this.showFormError('入力内容を確認してください');
}
}
async submitForm(form) {
console.log('フォームを送信中...');
// 送信処理をシミュレート
return new Promise((resolve) => {
setTimeout(() => {
console.log('送信完了!');
this.showFormSuccess('送信が完了しました');
form.reset();
resolve();
}, 2000);
});
}
showFormError(message) {
this.showFormMessage(message, 'red');
}
showFormSuccess(message) {
this.showFormMessage(message, 'green');
}
showFormMessage(message, color) {
let messageElement = this.form.querySelector('.form-message');
if (!messageElement) {
messageElement = document.createElement('div');
messageElement.className = 'form-message';
this.form.insertBefore(messageElement, this.form.firstChild);
}
messageElement.textContent = message;
messageElement.style.color = color;
messageElement.style.padding = '10px';
messageElement.style.marginBottom = '10px';
messageElement.style.backgroundColor = color === 'red' ? '#ffe6e6' : '#e6ffe6';
messageElement.style.borderRadius = '4px';
}
}

使い方はとても簡単です。

// バリデーターの初期化
const validator = new FormValidator('#contact-form');

これで、フィールドから離れた時(blur)にバリデーションが実行されます。

カスタムバリデーション

独自のバリデーションルールも追加できます。

// カスタムバリデーションの例
class AdvancedValidator extends FormValidator {
validateField(field) {
// 基本バリデーションを実行
let isValid = super.validateField(field);
// カスタムバリデーション
if (field.name === 'password') {
if (!this.isStrongPassword(field.value)) {
this.markFieldAsInvalid(field, 'パスワードは8文字以上で大文字・小文字・数字を含んでください');
isValid = false;
}
}
if (field.name === 'phone') {
if (!this.isValidPhoneNumber(field.value)) {
this.markFieldAsInvalid(field, '正しい電話番号形式で入力してください');
isValid = false;
}
}
return isValid;
}
isStrongPassword(password) {
const hasLowerCase = /[a-z]/.test(password);
const hasUpperCase = /[A-Z]/.test(password);
const hasNumbers = /\d/.test(password);
const isLongEnough = password.length >= 8;
return hasLowerCase && hasUpperCase && hasNumbers && isLongEnough;
}
isValidPhoneNumber(phone) {
const phoneRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/;
return phoneRegex.test(phone);
}
}

高度なバリデーションも簡単に追加できます。

非同期送信でスムーズな体験を

Fetch APIを使った送信

ページをリロードせずにデータを送信する方法です。

// 非同期送信の実装
class AsyncFormSubmitter {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.setupAsyncSubmission();
}
setupAsyncSubmission() {
this.form.addEventListener('submit', (event) => {
event.preventDefault();
this.handleAsyncSubmission(event);
});
}
async handleAsyncSubmission(event) {
try {
// 送信前の準備
this.showStatus('送信中...', 'info');
this.disableForm();
// フォームデータの準備
const formData = new FormData(event.target);
// サーバーに送信
const response = await this.submitToServer(formData);
// 成功時の処理
this.handleSuccess(response);
} catch (error) {
// エラー時の処理
this.handleError(error);
} finally {
// 後処理
this.enableForm();
}
}
async submitToServer(formData) {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
handleSuccess(response) {
this.showStatus('送信が完了しました!', 'success');
// フォームをリセット
this.form.reset();
// リダイレクトがある場合
if (response.redirectUrl) {
setTimeout(() => {
window.location.href = response.redirectUrl;
}, 2000);
}
}
handleError(error) {
console.error('送信エラー:', error);
this.showStatus(`送信エラー: ${error.message}`, 'error');
}
showStatus(message, type) {
let statusElement = this.form.querySelector('.status-message');
if (!statusElement) {
statusElement = document.createElement('div');
statusElement.className = 'status-message';
this.form.appendChild(statusElement);
}
const colors = {
info: '#007bff',
success: '#28a745',
error: '#dc3545'
};
statusElement.textContent = message;
statusElement.style.cssText = `
padding: 10px;
margin: 10px 0;
border-radius: 4px;
color: white;
background-color: ${colors[type]};
`;
}
disableForm() {
const elements = this.form.querySelectorAll('input, textarea, button');
elements.forEach(element => element.disabled = true);
}
enableForm() {
const elements = this.form.querySelectorAll('input, textarea, button');
elements.forEach(element => element.disabled = false);
}
}

使い方も簡単です。

// 非同期送信の初期化
const asyncSubmitter = new AsyncFormSubmitter('#contact-form');

これで、ページリロードなしでフォームを送信できます。

プログレスバーを追加

送信中の進捗を表示してみましょう。

// プログレスバー付きの送信
class ProgressFormSubmitter extends AsyncFormSubmitter {
async handleAsyncSubmission(event) {
try {
this.showProgress();
await super.handleAsyncSubmission(event);
} finally {
this.hideProgress();
}
}
showProgress() {
// プログレスバーの作成
const progressHTML = `
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<div class="progress-text">送信中... 0%</div>
<button type="button" class="cancel-button">キャンセル</button>
</div>
`;
const progressElement = document.createElement('div');
progressElement.innerHTML = progressHTML;
// キャンセルボタンの処理
const cancelButton = progressElement.querySelector('.cancel-button');
cancelButton.addEventListener('click', () => {
this.cancelSubmission();
});
this.form.appendChild(progressElement.firstElementChild);
// プログレスアニメーション
this.animateProgress();
}
animateProgress() {
const progressFill = this.form.querySelector('.progress-fill');
const progressText = this.form.querySelector('.progress-text');
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress > 90) progress = 90;
progressFill.style.width = `${progress}%`;
progressText.textContent = `送信中... ${Math.round(progress)}%`;
if (progress >= 90) {
clearInterval(interval);
}
}, 200);
this.progressInterval = interval;
}
hideProgress() {
if (this.progressInterval) {
clearInterval(this.progressInterval);
}
const progressContainer = this.form.querySelector('.progress-container');
if (progressContainer) {
// 100%まで進めてから削除
const progressFill = progressContainer.querySelector('.progress-fill');
const progressText = progressContainer.querySelector('.progress-text');
progressFill.style.width = '100%';
progressText.textContent = '完了!';
setTimeout(() => {
progressContainer.remove();
}, 1000);
}
}
cancelSubmission() {
if (confirm('送信を中止しますか?')) {
this.hideProgress();
this.showStatus('送信をキャンセルしました', 'info');
}
}
}

ユーザーは送信の進捗が分かって安心です。

エラーハンドリングで安心を提供

分かりやすいエラー表示

エラーが起きた時に、ユーザーが理解しやすいメッセージを表示しましょう。

// エラーハンドリング強化版
class RobustFormSubmitter extends AsyncFormSubmitter {
async submitToServer(formData) {
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const responseData = await response.json();
if (!response.ok) {
// サーバーからのエラーレスポンス処理
if (response.status === 422) {
// バリデーションエラー
this.handleValidationErrors(responseData.errors);
throw new Error('入力内容に問題があります');
} else if (response.status === 500) {
throw new Error('サーバーエラーが発生しました。しばらく待ってから再試行してください');
} else {
throw new Error(responseData.message || '送信に失敗しました');
}
}
return responseData;
} catch (error) {
if (error.name === 'TypeError') {
// ネットワークエラー
throw new Error('インターネット接続を確認してください');
} else {
throw error;
}
}
}
handleValidationErrors(errors) {
// サーバーから返されたフィールドエラーを表示
Object.entries(errors).forEach(([fieldName, messages]) => {
const field = this.form.querySelector(`[name="${fieldName}"]`);
if (field) {
this.showFieldError(field, Array.isArray(messages) ? messages[0] : messages);
}
});
}
showFieldError(field, message) {
field.style.borderColor = 'red';
// 既存のエラーメッセージを削除
const existingError = field.parentNode.querySelector('.field-error');
if (existingError) {
existingError.remove();
}
// 新しいエラーメッセージを作成
const errorElement = document.createElement('div');
errorElement.className = 'field-error';
errorElement.textContent = message;
errorElement.style.cssText = 'color: red; font-size: 12px; margin-top: 2px;';
field.parentNode.appendChild(errorElement);
}
handleError(error) {
super.handleError(error);
// エラーの種類に応じた追加処理
if (error.message.includes('ネットワーク')) {
this.showRetryOption();
} else if (error.message.includes('サーバーエラー')) {
this.showContactInfo();
}
}
showRetryOption() {
const retryButton = document.createElement('button');
retryButton.textContent = '再試行';
retryButton.type = 'button';
retryButton.style.cssText = 'margin-left: 10px; padding: 5px 10px;';
retryButton.addEventListener('click', () => {
retryButton.remove();
const submitEvent = new Event('submit');
this.form.dispatchEvent(submitEvent);
});
const statusElement = this.form.querySelector('.status-message');
if (statusElement) {
statusElement.appendChild(retryButton);
}
}
showContactInfo() {
const contactInfo = document.createElement('div');
contactInfo.innerHTML = `
<p style="margin-top: 10px; font-size: 12px;">
問題が続く場合は、お手数ですが
<a href="mailto:support@example.com">support@example.com</a>
までご連絡ください。
</p>
`;
const statusElement = this.form.querySelector('.status-message');
if (statusElement) {
statusElement.appendChild(contactInfo);
}
}
}

エラーが起きても、ユーザーは次に何をすればいいかが分かります。

実践例:完全なお問い合わせフォーム

最後に、すべての機能を組み合わせた完全な例を見てみましょう。

<!-- 完全版HTML -->
<form id="complete-contact-form">
<div class="form-group">
<label for="name">お名前 *</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">メールアドレス *</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">電話番号</label>
<input type="tel" id="phone" name="phone">
</div>
<div class="form-group">
<label for="subject">件名 *</label>
<select id="subject" name="subject" required>
<option value="">選択してください</option>
<option value="inquiry">お問い合わせ</option>
<option value="support">サポート</option>
<option value="other">その他</option>
</select>
</div>
<div class="form-group">
<label for="message">メッセージ *</label>
<textarea id="message" name="message" required rows="5"></textarea>
</div>
<button type="submit">送信する</button>
</form>

そして、すべての機能を統合したJavaScript:

// 完全版のフォーム制御
class CompleteFormController {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.initialize();
}
initialize() {
// バリデーション、非同期送信、エラーハンドリングを統合
this.validator = new AdvancedValidator(this.form);
this.submitter = new RobustFormSubmitter(this.form);
// カスタム初期化
this.setupCustomFeatures();
}
setupCustomFeatures() {
// 送信ボタンの状態管理
this.updateSubmitButton();
// リアルタイムで送信ボタンの状態を更新
const inputs = this.form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('input', () => {
this.updateSubmitButton();
});
});
}
updateSubmitButton() {
const submitButton = this.form.querySelector('button[type="submit"]');
const requiredFields = this.form.querySelectorAll('[required]');
const allFilled = Array.from(requiredFields).every(field =>
field.value.trim() !== ''
);
submitButton.disabled = !allFilled;
submitButton.style.opacity = allFilled ? '1' : '0.5';
}
}
// 使用開始
const completeForm = new CompleteFormController('#complete-contact-form');

これで、プロレベルのフォーム制御が完成です!

まとめ

JavaScriptでのフォーム送信制御について学びました。

重要なポイント

  • preventDefault()でデフォルト送信を制御
  • リアルタイムバリデーションでユーザビリティ向上
  • 非同期送信でスムーズな体験を提供
  • 分かりやすいエラーハンドリングで安心感

実装時のコツ

  • ユーザーの操作に対してすぐに反応する
  • エラーメッセージは具体的で理解しやすく
  • 送信中の状態を明確に表示する
  • セキュリティはサーバーサイドでも確保

次のステップ

  • ファイルアップロード機能の追加
  • より高度なバリデーションルール
  • A/Bテストでユーザビリティ改善
  • アクセシビリティの向上

フォーム送信制御をマスターすると、ユーザーが「使いやすい!」と感じるWebアプリが作れるようになります。 最初は簡単な機能から始めて、だんだんと高度な機能も追加してみてくださいね。

ぜひ今日から、これらの技術を活用して、ユーザーフレンドリーなフォームを作ってみませんか?

関連記事