📝Forms & Validation

Bygg säkra, användarvänliga formulär med modern validering. Från HTML5-attribut till avancerad JavaScript-validering.

Kodexempel

📝

HTML5 Form Validation

Modern formulärvalidering med HTML5 attribut och JavaScript.

<form id="contactForm" novalidate>
    <div className="form-group">
        <label for="name">Namn *</label>
        <input 
            type="text" 
            id="name" 
            name="name" 
            required 
            minlength="2"
            pattern="[A-Za-zÅÄÖåäö\s]+"
            className="form-control"
        >
        <span className="error-message"></span>
    </div>
    
    <div className="form-group">
        <label for="email">E-post *</label>
        <input 
            type="email" 
            id="email" 
            name="email" 
            required
            className="form-control"
        >
        <span className="error-message"></span>
    </div>
    
    <div className="form-group">
        <label for="phone">Telefon</label>
        <input 
            type="tel" 
            id="phone" 
            name="phone"
            pattern="[0-9+\s\-()]+"
            className="form-control"
        >
        <span className="error-message"></span>
    </div>
    
    <div className="form-group">
        <label for="message">Meddelande *</label>
        <textarea 
            id="message" 
            name="message" 
            required
            minlength="10"
            maxlength="500"
            rows="4"
            className="form-control"
        ></textarea>
        <span className="char-count">0/500</span>
    </div>
    
    <button type="submit" className="btn btn-primary">Skicka</button>
</form>

HTML5 validering med required, pattern, minlength och type attribut

JavaScript Validation

Custom validering med JavaScript för avancerad kontroll.

// Form validation class
class FormValidator {
    constructor(form) {
        this.form = form;
        this.errors = {};
        this.init();
    }
    
    init() {
        this.form.addEventListener('submit', (e) => {
            e.preventDefault();
            if (this.validate()) {
                this.submitForm();
            }
        });
        
        // Real-time validation
        this.form.querySelectorAll('input, textarea').forEach(field => {
            field.addEventListener('blur', () => this.validateField(field));
            field.addEventListener('input', () => this.clearError(field));
        });
    }
    
    validate() {
        this.errors = {};
        const fields = this.form.querySelectorAll('[required]');
        
        fields.forEach(field => {
            this.validateField(field);
        });
        
        return Object.keys(this.errors).length === 0;
    }
    
    validateField(field) {
        const value = field.value.trim();
        const name = field.name;
        
        // Required validation
        if (field.required && !value) {
            this.setError(field, 'Detta fält är obligatoriskt');
            return;
        }
        
        // Email validation
        if (field.type === 'email' && value) {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (!emailRegex.test(value)) {
                this.setError(field, 'Ange en giltig e-postadress');
            }
        }
        
        // Pattern validation
        if (field.pattern && value) {
            const regex = new RegExp(field.pattern);
            if (!regex.test(value)) {
                this.setError(field, 'Ogiltigt format');
            }
        }
        
        // Custom validations
        this.customValidations(field);
    }
    
    customValidations(field) {
        const value = field.value.trim();
        
        // Swedish personal number
        if (field.name === 'personnummer' && value) {
            if (!this.validatePersonnummer(value)) {
                this.setError(field, 'Ogiltigt personnummer');
            }
        }
        
        // Password strength
        if (field.type === 'password' && value) {
            const strength = this.checkPasswordStrength(value);
            if (strength < 3) {
                this.setError(field, 'Lösenordet är för svagt');
            }
        }
    }
    
    setError(field, message) {
        this.errors[field.name] = message;
        const errorElement = field.parentElement.querySelector('.error-message');
        if (errorElement) {
            errorElement.textContent = message;
            field.classList.add('error');
        }
    }
    
    clearError(field) {
        delete this.errors[field.name];
        const errorElement = field.parentElement.querySelector('.error-message');
        if (errorElement) {
            errorElement.textContent = '';
            field.classList.remove('error');
        }
    }
}

// Initialize validator
const form = document.getElementById('contactForm');
new FormValidator(form);

Omfattande JavaScript-validering med realtidsfeedback och custom regler

📎

File Upload Handling

Säker filuppladdning med validering och förhandsgranskning.

// File upload component
class FileUploader {
    constructor(config) {
        this.input = document.getElementById(config.inputId);
        this.preview = document.getElementById(config.previewId);
        this.maxSize = config.maxSize || 5 * 1024 * 1024; // 5MB
        this.allowedTypes = config.allowedTypes || ['image/jpeg', 'image/png', 'image/gif'];
        
        this.init();
    }
    
    init() {
        this.input.addEventListener('change', (e) => this.handleFiles(e.target.files));
        
        // Drag and drop
        const dropZone = this.input.closest('.drop-zone');
        if (dropZone) {
            dropZone.addEventListener('dragover', (e) => {
                e.preventDefault();
                dropZone.classList.add('drag-over');
            });
            
            dropZone.addEventListener('dragleave', () => {
                dropZone.classList.remove('drag-over');
            });
            
            dropZone.addEventListener('drop', (e) => {
                e.preventDefault();
                dropZone.classList.remove('drag-over');
                this.handleFiles(e.dataTransfer.files);
            });
        }
    }
    
    handleFiles(files) {
        Array.from(files).forEach(file => {
            if (this.validateFile(file)) {
                this.previewFile(file);
                this.uploadFile(file);
            }
        });
    }
    
    validateFile(file) {
        // Check file type
        if (!this.allowedTypes.includes(file.type)) {
            alert(`Filtypen ${file.type} är inte tillåten`);
            return false;
        }
        
        // Check file size
        if (file.size > this.maxSize) {
            alert(`Filen är för stor. Max storlek: ${this.maxSize / 1024 / 1024}MB`);
            return false;
        }
        
        return true;
    }
    
    previewFile(file) {
        if (file.type.startsWith('image/')) {
            const reader = new FileReader();
            reader.onload = (e) => {
                const img = document.createElement('img');
                img.src = e.target.result;
                img.classList.add('preview-image');
                this.preview.appendChild(img);
            };
            reader.readAsDataURL(file);
        }
    }
    
    async uploadFile(file) {
        const formData = new FormData();
        formData.append('file', file);
        
        try {
            const response = await fetch('/api/upload', {
                method: 'POST',
                body: formData
            });
            
            if (response.ok) {
                const data = await response.json();
                console.log('Upload successful:', data);
            } else {
                throw new Error('Upload failed');
            }
        } catch (error) {
            console.error('Upload error:', error);
        }
    }
}

// Initialize uploader
new FileUploader({
    inputId: 'fileInput',
    previewId: 'filePreview',
    maxSize: 10 * 1024 * 1024, // 10MB
    allowedTypes: ['image/jpeg', 'image/png', 'application/pdf']
});

Komplett filuppladdning med drag & drop, validering och förhandsgranskning

🔒

Form Security

Säkerhetsåtgärder för formulär mot vanliga attacker.

// CSRF Token handling
class SecureForm {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.csrfToken = this.getCSRFToken();
        this.init();
    }
    
    init() {
        // Add CSRF token to form
        const tokenInput = document.createElement('input');
        tokenInput.type = 'hidden';
        tokenInput.name = '_csrf';
        tokenInput.value = this.csrfToken;
        this.form.appendChild(tokenInput);
        
        // Sanitize inputs
        this.form.addEventListener('submit', (e) => {
            e.preventDefault();
            this.sanitizeInputs();
            this.submitSecurely();
        });
        
        // Rate limiting
        this.setupRateLimit();
    }
    
    getCSRFToken() {
        // Get from meta tag or cookie
        const meta = document.querySelector('meta[name="csrf-token"]');
        return meta ? meta.content : this.getCookie('XSRF-TOKEN');
    }
    
    sanitizeInputs() {
        const inputs = this.form.querySelectorAll('input[type="text"], textarea');
        inputs.forEach(input => {
            // Basic XSS prevention
            input.value = this.escapeHtml(input.value);
        });
    }
    
    escapeHtml(text) {
        const map = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#039;'
        };
        return text.replace(/[&<>"']/g, m => map[m]);
    }
    
    setupRateLimit() {
        let submitCount = 0;
        const maxSubmits = 3;
        const resetTime = 60000; // 1 minute
        
        this.form.addEventListener('submit', (e) => {
            submitCount++;
            if (submitCount > maxSubmits) {
                e.preventDefault();
                alert('För många försök. Vänta en minut.');
                setTimeout(() => { submitCount = 0; }, resetTime);
            }
        });
    }
    
    async submitSecurely() {
        const formData = new FormData(this.form);
        
        try {
            const response = await fetch(this.form.action, {
                method: 'POST',
                headers: {
                    'X-CSRF-Token': this.csrfToken
                },
                body: formData,
                credentials: 'same-origin'
            });
            
            if (response.ok) {
                // Handle success
                window.location.href = '/success';
            } else {
                // Handle errors
                const error = await response.json();
                this.displayError(error.message);
            }
        } catch (error) {
            console.error('Submission error:', error);
        }
    }
}

// Initialize secure form
new SecureForm('secureContactForm');

Säkerhetsimplementering med CSRF-skydd, input-sanering och rate limiting

Best Practices

👤

Användarvänlighet

  • Tydliga labels och instruktioner
  • Logisk tabb-ordning
  • Inline validering med direkt feedback
  • Spara formulärdata lokalt vid sidladdning
🔐

Säkerhet

  • Validera alltid på serversidan
  • Använd HTTPS för alla formulär
  • Implementera CSRF-skydd
  • Sanera all användarinput

Tillgänglighet

  • Använd semantiska HTML-element
  • Koppla labels till inputs med 'for'
  • Ge tydliga felmeddelanden
  • Stöd tangentbordsnavigering

Performance

  • Debounce realtidsvalidering
  • Lazy load stora formulär
  • Optimera filuppladdningar
  • Använd Web Workers för tung validering

🎯 HTML5 Input Types

Text Inputs

  • type="text" - Vanlig text
  • type="email" - E-postadress
  • type="tel" - Telefonnummer
  • type="url" - Webbadress
  • type="search" - Sökfält

Date & Time

  • type="date" - Datum
  • type="time" - Tid
  • type="datetime-local" - Datum & tid
  • type="month" - Månad
  • type="week" - Vecka

Numeric & Other

  • type="number" - Nummer
  • type="range" - Slider
  • type="color" - Färgväljare
  • type="file" - Filuppladdning
  • type="hidden" - Dolt fält

🔍 Vanliga valideringsmönster

Svenska format

  • Personnummer: [0-9]{6}-?[0-9]{4}
  • Postnummer: [0-9]{3}\s?[0-9]{2}
  • Telefon: 0[0-9]{1,3}-?[0-9]{5,8}
  • Bankgiro: [0-9]{3,4}-[0-9]{4}

Allmänna format

  • E-post: [a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}
  • URL: https?://[\w\-._~:/?#[\]@!$&'()*+,;=.]+
  • Lösenord (stark): (?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}
  • Hex-färg: #[0-9A-Fa-f]{6}