5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フォームのバリデーションのバリエーション(JavaScript)

Posted at

前回のこの記事をJavaScriptにしました。
https://qiita.com/WebDesignGaju/items/7a6f3a68c3a0d690c7ed

主な変換ポイント

1. jQuery → Vanilla JavaScript の基本変換

  • $(document).ready()document.addEventListener('DOMContentLoaded')
  • $('selector')document.querySelector() / document.querySelectorAll()
  • .on('event', function).addEventListener('event', function)
  • .addClass()/.removeClass().classList.add()/.classList.remove()

2. DOM操作の変換

  • .after()parentNode.insertBefore(element, nextSibling)
  • .siblings()parentNode.querySelector() + 条件
  • .remove()parentNode.removeChild() or .remove()
  • .text().textContent
  • .val().value

3. イベント処理の改善

  • 要素の存在チェック(if (element))を追加
  • forEachでイベントリスナーを一括登録
  • メモリリーク防止のため適切なイベント管理

4. エラーハンドリングの強化

  • nullチェックを追加
  • 要素が存在しない場合の処理
  • 安全なDOM操作

5. パフォーマンス最適化

  • 不要なDOM検索を削減
  • イベント委譲の活用
  • 効率的な要素操作
document.addEventListener('DOMContentLoaded', function() {

// ===== 基本的なバリデーション =====

// 1. 必須項目チェック
const nameField = document.querySelector('input[name="your-name"]');
if (nameField) {
    nameField.addEventListener('blur', function() {
        if (this.value.trim() === '') {
            this.classList.add('wpcf7-not-valid');
            showError(this, '名前を入力してください');
        } else {
            this.classList.remove('wpcf7-not-valid');
            hideError(this);
        }
    });
}

// 2. メールアドレス形式チェック
const emailField = document.querySelector('input[name="your-email"]');
if (emailField) {
    emailField.addEventListener('blur', function() {
        const email = this.value.trim();
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        
        if (!emailRegex.test(email)) {
            this.classList.add('wpcf7-not-valid');
            showError(this, '正しいメールアドレスを入力してください');
        } else {
            this.classList.remove('wpcf7-not-valid');
            hideError(this);
        }
    });
}

// 3. 文字数制限チェック
if (nameField) {
    nameField.addEventListener('input', function() {
        const value = this.value;
        const minLength = 2;
        const maxLength = 50;
        
        if (value.length < minLength) {
            showError(this, `名前は${minLength}文字以上で入力してください`);
        } else if (value.length > maxLength) {
            showError(this, `名前は${maxLength}文字以下で入力してください`);
        } else {
            hideError(this);
        }
    });
}

// 4. 電話番号形式チェック
const telField = document.querySelector('input[name="your-tel"]');
if (telField) {
    telField.addEventListener('input', function() {
        const tel = this.value.replace(/[^\d-]/g, '');
        const telRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/;
        
        if (tel && !telRegex.test(tel)) {
            showError(this, '正しい電話番号を入力してください(例: 03-1234-5678)');
        } else {
            hideError(this);
        }
    });
}

// 5. 郵便番号チェック
const zipField = document.querySelector('input[name="your-zip"]');
if (zipField) {
    zipField.addEventListener('input', function() {
        const zip = this.value.replace(/[^\d-]/g, '');
        const zipRegex = /^\d{3}-\d{4}$/;
        
        if (zip && !zipRegex.test(zip)) {
            showError(this, '郵便番号は123-4567の形式で入力してください');
        } else {
            hideError(this);
        }
    });
}

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

// 6. 入力時即座チェック
if (nameField) {
    nameField.addEventListener('input', function() {
        const value = this.value.trim();
        
        if (value.length === 0) {
            clearValidation(this);
        } else if (value.length < 2) {
            showError(this, '名前は2文字以上で入力してください');
        } else if (!/^[ぁ-んァ-ヶー一-龠\s]+$/.test(value)) {
            showError(this, '日本語で入力してください');
        } else {
            showSuccess(this, '正しく入力されています');
        }
    });
}

// 7. メールアドレスリアルタイム
if (emailField) {
    emailField.addEventListener('input', function() {
        const email = this.value.trim();
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        
        if (email.length === 0) {
            clearValidation(this);
        } else if (!emailRegex.test(email)) {
            showError(this, '正しいメールアドレス形式ではありません');
        } else {
            showSuccess(this, '正しいメールアドレスです');
        }
    });
}

// 8. パスワード強度チェック
const passwordField = document.querySelector('input[name="your-password"]');
if (passwordField) {
    passwordField.addEventListener('input', function() {
        const password = this.value;
        const strength = calculatePasswordStrength(password);
        
        if (password.length === 0) {
            clearValidation(this);
        } else if (strength < 3) {
            showError(this, 'パスワードが弱すぎます');
        } else if (strength < 5) {
            showWarning(this, 'パスワードの強度は普通です');
        } else {
            showSuccess(this, '強固なパスワードです');
        }
    });
}

// ===== もうちょっと踏み込んだバリデーション =====

// 9. 文字数カウント表示
const messageField = document.querySelector('textarea[name="your-message"]');
if (messageField) {
    messageField.addEventListener('input', function() {
        const value = this.value;
        const maxLength = 500;
        const currentLength = value.length;
        
        // カウンター表示
        let counter = this.nextElementSibling;
        if (!counter || !counter.classList.contains('char-counter')) {
            counter = document.createElement('div');
            counter.className = 'char-counter';
            this.parentNode.insertBefore(counter, this.nextSibling);
        }
        counter.textContent = `${currentLength}/${maxLength}`;
        
        // 制限チェック
        if (currentLength > maxLength) {
            this.classList.add('wpcf7-not-valid');
            showError(this, `文字数が上限を超えています(${currentLength}/${maxLength})`);
        } else {
            this.classList.remove('wpcf7-not-valid');
            hideError(this);
        }
    });
}

// 10. 数値範囲チェック
const ageField = document.querySelector('input[name="your-age"]');
if (ageField) {
    ageField.addEventListener('input', function() {
        const value = parseInt(this.value);
        const min = 18;
        const max = 100;
        
        if (isNaN(value)) {
            showError(this, '数値を入力してください');
        } else if (value < min || value > max) {
            showError(this, `年齢は${min}歳から${max}歳の範囲で入力してください`);
        } else {
            showSuccess(this, '正しい年齢です');
        }
    });
}

// 11. 日付バリデーション
const dateField = document.querySelector('input[name="your-date"]');
if (dateField) {
    dateField.addEventListener('change', function() {
        const selectedDate = new Date(this.value);
        const today = new Date();
        const minDate = new Date();
        minDate.setFullYear(today.getFullYear() - 1);
        
        if (selectedDate < minDate) {
            showError(this, '1年以内の日付を選択してください');
        } else if (selectedDate > today) {
            showError(this, '今日以前の日付を選択してください');
        } else {
            showSuccess(this, '正しい日付です');
        }
    });
}

// 12. ファイルバリデーション
const fileField = document.querySelector('input[name="your-file"]');
if (fileField) {
    fileField.addEventListener('change', function() {
        const file = this.files[0];
        const maxSize = 5 * 1024 * 1024; // 5MB
        const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
        
        if (!file) {
            clearValidation(this);
            return;
        }
        
        if (file.size > maxSize) {
            showError(this, 'ファイルサイズは5MB以下にしてください');
        } else if (!allowedTypes.includes(file.type)) {
            showError(this, '許可されていないファイル形式です');
        } else {
            showSuccess(this, 'ファイルが正常に選択されました');
        }
    });
}

// ===== Contact Form 7 統合 =====

// 13. CF7送信前バリデーション
document.addEventListener('wpcf7beforesubmit', function(event) {
    const form = event.target;
    let isValid = true;
    
    // カスタムバリデーション
    const nameField = form.querySelector('input[name="your-name"]');
    if (nameField && nameField.value.trim().length < 2) {
        nameField.classList.add('wpcf7-not-valid');
        isValid = false;
    }
    
    const emailField = form.querySelector('input[name="your-email"]');
    if (emailField && !validateEmail(emailField.value)) {
        emailField.classList.add('wpcf7-not-valid');
        isValid = false;
    }
    
    if (!isValid) {
        event.preventDefault();
    }
}, false);

// 14. CF7送信後処理
document.addEventListener('wpcf7mailsent', function(event) {
    // 送信成功時の処理
    const form = event.target;
    
    // フォームリセット
    form.reset();
    
    // 成功メッセージ表示
    showSuccessMessage('メールが正常に送信されました!');
    
    // Google Analytics イベント送信
    if (typeof gtag !== 'undefined') {
        gtag('event', 'form_submit', {
            'event_category': 'contact',
            'event_label': 'contact_form_7'
        });
    }
}, false);

// 15. CF7エラーハンドリング
document.addEventListener('wpcf7invalid', function(event) {
    const form = event.target;
    const invalidFields = form.querySelectorAll('.wpcf7-not-valid');
    
    // 最初のエラーフィールドにフォーカス
    if (invalidFields.length > 0) {
        invalidFields[0].focus();
        invalidFields[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    
    // カスタムエラーメッセージ
    invalidFields.forEach(field => {
        addCustomErrorMessage(field);
    });
}, false);

// 16. リアルタイムエラー表示クリア
const formFields = document.querySelectorAll('.wpcf7-form input, .wpcf7-form textarea, .wpcf7-form select');
formFields.forEach(field => {
    ['input', 'change'].forEach(eventType => {
        field.addEventListener(eventType, function() {
            this.classList.remove('wpcf7-not-valid');
            const errorTip = this.parentNode.querySelector('.wpcf7-not-valid-tip');
            if (errorTip) {
                errorTip.remove();
            }
        });
    });
});

// ===== 複合バリデーション =====

// 17. 複数フィールド相互チェック
const emailConfirmField = document.querySelector('input[name="your-email-confirm"]');
if (emailField && emailConfirmField) {
    [emailField, emailConfirmField].forEach(field => {
        field.addEventListener('input', function() {
            const email = emailField.value;
            const emailConfirm = emailConfirmField.value;
            
            if (email && emailConfirm && email !== emailConfirm) {
                showError(emailConfirmField, 'メールアドレスが一致しません');
            } else {
                hideError(emailConfirmField);
            }
        });
    });
}

// 18. 条件付きバリデーション
const inquiryTypeField = document.querySelector('select[name="your-inquiry-type"]');
const companyField = document.querySelector('input[name="your-company"]');
if (inquiryTypeField && companyField) {
    inquiryTypeField.addEventListener('change', function() {
        const inquiryType = this.value;
        
        if (inquiryType === 'business') {
            companyField.setAttribute('required', true);
            const formGroup = companyField.closest('.form-group');
            if (formGroup) {
                formGroup.classList.add('required');
            }
        } else {
            companyField.removeAttribute('required');
            const formGroup = companyField.closest('.form-group');
            if (formGroup) {
                formGroup.classList.remove('required');
            }
        }
    });
}

// 19. 動的フィールド追加時のバリデーション
const addFieldBtn = document.querySelector('.add-field-btn');
if (addFieldBtn) {
    addFieldBtn.addEventListener('click', function() {
        const newField = document.createElement('input');
        newField.type = 'text';
        newField.name = 'dynamic-field[]';
        newField.className = 'dynamic-field';
        
        // 新しく追加されたフィールドにバリデーションを適用
        newField.addEventListener('input', function() {
            if (this.value.trim() === '') {
                showError(this, 'この項目は必須です');
            } else {
                hideError(this);
            }
        });
        
        const container = document.querySelector('.dynamic-fields-container');
        if (container) {
            container.appendChild(newField);
        }
    });
}

// 20. 全フィールド一括バリデーション
const forms = document.querySelectorAll('.wpcf7-form');
forms.forEach(form => {
    form.addEventListener('submit', function(e) {
        let isValid = true;
        
        // すべての必須フィールドをチェック
        const requiredFields = form.querySelectorAll('[required]');
        requiredFields.forEach(field => {
            if (field.value.trim() === '') {
                field.classList.add('wpcf7-not-valid');
                showError(field, 'この項目は必須です');
                isValid = false;
            }
        });
        
        // メールアドレスフィールドをチェック
        const emailFields = form.querySelectorAll('input[type="email"]');
        emailFields.forEach(field => {
            if (field.value && !validateEmail(field.value)) {
                field.classList.add('wpcf7-not-valid');
                showError(field, '正しいメールアドレスを入力してください');
                isValid = false;
            }
        });
        
        if (!isValid) {
            e.preventDefault();
        }
    });
});

});

// ===== ヘルパー関数 =====

// エラーメッセージ表示
function showError(element, message) {
    element.classList.add('wpcf7-not-valid');
    
    // 既存のエラーメッセージを削除
    const existingError = element.parentNode.querySelector('.error-message');
    if (existingError) {
        existingError.remove();
    }
    
    // 新しいエラーメッセージを作成
    const errorSpan = document.createElement('span');
    errorSpan.className = 'error-message';
    errorSpan.style.color = 'red';
    errorSpan.style.fontSize = '12px';
    errorSpan.textContent = message;
    
    element.parentNode.insertBefore(errorSpan, element.nextSibling);
}

// 成功メッセージ表示
function showSuccess(element, message) {
    element.classList.remove('wpcf7-not-valid');
    element.classList.add('valid');
    
    // 既存の成功メッセージを削除
    const existingSuccess = element.parentNode.querySelector('.success-message');
    if (existingSuccess) {
        existingSuccess.remove();
    }
    
    // 新しい成功メッセージを作成
    const successSpan = document.createElement('span');
    successSpan.className = 'success-message';
    successSpan.style.color = 'green';
    successSpan.style.fontSize = '12px';
    successSpan.textContent = message;
    
    element.parentNode.insertBefore(successSpan, element.nextSibling);
}

// 警告メッセージ表示
function showWarning(element, message) {
    element.classList.remove('wpcf7-not-valid');
    element.classList.add('warning');
    
    // 既存の警告メッセージを削除
    const existingWarning = element.parentNode.querySelector('.warning-message');
    if (existingWarning) {
        existingWarning.remove();
    }
    
    // 新しい警告メッセージを作成
    const warningSpan = document.createElement('span');
    warningSpan.className = 'warning-message';
    warningSpan.style.color = 'orange';
    warningSpan.style.fontSize = '12px';
    warningSpan.textContent = message;
    
    element.parentNode.insertBefore(warningSpan, element.nextSibling);
}

// エラーメッセージ非表示
function hideError(element) {
    element.classList.remove('wpcf7-not-valid');
    const errorMessage = element.parentNode.querySelector('.error-message');
    if (errorMessage) {
        errorMessage.remove();
    }
}

// バリデーション状態クリア
function clearValidation(element) {
    element.classList.remove('wpcf7-not-valid', 'valid', 'warning');
    const messages = element.parentNode.querySelectorAll('.error-message, .success-message, .warning-message');
    messages.forEach(msg => msg.remove());
}

// メールアドレス検証
function validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

// パスワード強度計算
function calculatePasswordStrength(password) {
    let strength = 0;
    
    if (password.length >= 8) strength++;
    if (password.length >= 12) strength++;
    if (/[a-z]/.test(password)) strength++;
    if (/[A-Z]/.test(password)) strength++;
    if (/[0-9]/.test(password)) strength++;
    if (/[^A-Za-z0-9]/.test(password)) strength++;
    
    return strength;
}

// 全角・半角変換
function toHalfWidth(str) {
    return str.replace(/[A-Za-z0-9]/g, function(s) {
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
    });
}

// ひらがな・カタカナ変換
function toKatakana(str) {
    return str.replace(/[ぁ-ゖ]/g, function(match) {
        const chr = match.charCodeAt(0) + 0x60;
        return String.fromCharCode(chr);
    });
}

// 数値のみ入力制限
function numbersOnly(element) {
    element.addEventListener('input', function() {
        this.value = this.value.replace(/[^0-9]/g, '');
    });
}

// 日本語のみ入力制限
function japaneseOnly(element) {
    element.addEventListener('input', function() {
        const value = this.value;
        const japaneseRegex = /^[ぁ-んァ-ヶー一-龠\s]*$/;
        
        if (!japaneseRegex.test(value)) {
            this.value = value.replace(/[^ぁ-んァ-ヶー一-龠\s]/g, '');
        }
    });
}

// 英数字のみ入力制限
function alphanumericOnly(element) {
    element.addEventListener('input', function() {
        this.value = this.value.replace(/[^a-zA-Z0-9]/g, '');
    });
}

// フォーム送信前の最終チェック
function finalValidation(form) {
    let isValid = true;
    const errors = [];
    
    // 必須項目チェック
    const requiredFields = form.querySelectorAll('[required]');
    requiredFields.forEach(field => {
        if (field.value.trim() === '') {
            errors.push(field.getAttribute('name') + 'は必須項目です');
            isValid = false;
        }
    });
    
    // メールアドレスチェック
    const emailFields = form.querySelectorAll('input[type="email"]');
    emailFields.forEach(field => {
        if (field.value && !validateEmail(field.value)) {
            errors.push('正しいメールアドレスを入力してください');
            isValid = false;
        }
    });
    
    if (!isValid) {
        alert('以下のエラーを修正してください:\n' + errors.join('\n'));
    }
    
    return isValid;
}

// 成功メッセージ表示
function showSuccessMessage(message) {
    const successDiv = document.createElement('div');
    successDiv.className = 'success-notification';
    successDiv.textContent = message;
    successDiv.style.cssText = `
        position: fixed;
        top: 20px;
        right: 20px;
        background: #4CAF50;
        color: white;
        padding: 15px;
        border-radius: 5px;
        z-index: 9999;
        opacity: 1;
        transition: opacity 0.3s ease;
    `;
    
    document.body.appendChild(successDiv);
    
    setTimeout(() => {
        successDiv.style.opacity = '0';
        setTimeout(() => {
            if (successDiv.parentNode) {
                successDiv.parentNode.removeChild(successDiv);
            }
        }, 300);
    }, 3000);
}

// カスタムエラーメッセージ追加
function addCustomErrorMessage(field) {
    const fieldName = field.getAttribute('name');
    let customMessage = '';
    
    switch(fieldName) {
        case 'your-name':
            customMessage = 'お名前を正しく入力してください';
            break;
        case 'your-email':
            customMessage = 'メールアドレスの形式が正しくありません';
            break;
        case 'your-tel':
            customMessage = '電話番号を正しく入力してください';
            break;
        default:
            customMessage = 'この項目を正しく入力してください';
    }
    
    // 既存のエラーメッセージを削除
    const existingCustomError = field.parentNode.querySelector('.custom-error');
    if (existingCustomError) {
        existingCustomError.remove();
    }
    
    // カスタムエラーメッセージを追加
    const customErrorSpan = document.createElement('span');
    customErrorSpan.className = 'custom-error';
    customErrorSpan.style.cssText = 'color: red; font-size: 12px; display: block;';
    customErrorSpan.textContent = customMessage;
    
    field.parentNode.insertBefore(customErrorSpan, field.nextSibling);
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?