Skillversitas

47. Forms API: Advanced Form Handling in JavaScript

Introduction

The Forms API provides powerful ways to interact with HTML form elements. With the FormData object, you can easily collect, manipulate, and submit form data. The Forms API also provides built-in validation and various methods to control form submission.

For IT students building a campus portal, understanding the Forms API is crucial. Graduation registration forms, course registration forms, and assignment submission forms all need robust handling.

Why Is the Forms API Important?

Forms are the primary way users interact with web applications. In a digital campus, there are many forms that must be handled correctly:

  • Graduation registration form with complex validation
  • Online course registration form
  • Assignment submission form with file upload
  • Lecturer evaluation form
  • Facility borrowing form

Without a solid understanding of the Forms API, form handling becomes chaotic, with inconsistent validation and error-prone submission.

Accessing Form Elements

// Access a form by ID
const form = document.getElementById('registration-form');

// Access form elements by name
const nameInput = form.elements['full_name'];
const emailInput = form.elements.email;

// Iterate over all form elements
for (const element of form.elements) {
    console.log(`${element.name}: ${element.value}`);
}

// Access all inputs with a specific type
const textInputs = form.querySelectorAll('input[type="text"]');
const selects = form.querySelectorAll('select');
const textareas = form.querySelectorAll('textarea');

FormData Object

FormData is the modern way to collect form data:

// Create a FormData from a form
const form = document.getElementById('registration-form');
const formData = new FormData(form);

// Access the data
const name = formData.get('name');
const allValues = formData.getAll('hobbies'); // For checkboxes

// Add data manually
formData.append('timestamp', Date.now());
formData.append('source', 'web');

// Remove data
formData.delete('optional_field');

// Check whether a field exists
if (formData.has('email')) {
    console.log('Email field ada');
}

// Iterate over all data
for (const [key, value] of formData) {
    console.log(`${key}: ${value}`);
}

// Convert to a plain object
const dataObject = Object.fromEntries(formData.entries());
console.log(dataObject);

// Convert to JSON
const jsonData = JSON.stringify(Object.fromEntries(formData.entries()));

// Send with fetch
fetch('/api/registration', {
    method: 'POST',
    body: formData  // FormData automatically sets Content-Type: multipart/form-data
}).then(res => res.json())
  .then(data => console.log(data));

The Submit Event and preventDefault()

const form = document.getElementById('registration-form');

// Catch the submit event
form.addEventListener('submit', (event) => {
    // Prevent the default submit (page reload)
    event.preventDefault();

    // Collect the data
    const formData = new FormData(form);
    const data = Object.fromEntries(formData.entries());

    // Validate manually if needed
    if (!validateForm(data)) {
        return;
    }

    // Send the data
    submitRegistration(data);
});

// Validation example
function validateForm(data) {
    const errors = [];

    if (!data.name || data.name.trim().length < 3) {
        errors.push('Name must be at least 3 characters');
    }

    if (!data.email || !data.email.endsWith('@university.edu')) {
        errors.push('Email must use the university domain');
    }

    if (!data.studentId || !/^\d{10}$/.test(data.studentId)) {
        errors.push('StudentID must be 10 digits');
    }

    if (errors.length > 0) {
        showErrors(errors);
        return false;
    }

    return true;
}

function showErrors(errors) {
    const errorContainer = document.getElementById('form-errors');
    errorContainer.innerHTML = errors.map(err => `<p class="error">${err}</p>`).join('');
    errorContainer.style.display = 'block';
}

form.reset()

// Reset the form to its initial values
const form = document.getElementById('registration-form');
form.reset();

// Reset specific fields
function resetForm() {
    form.reset();

    // Reset custom state
    document.getElementById('file-preview').innerHTML = '';
    document.getElementById('error-messages').innerHTML = '';
    document.getElementById('submit-btn').disabled = false;

    // Reset validation states
    form.querySelectorAll('.is-valid, .is-invalid').forEach(el => {
        el.classList.remove('is-valid', 'is-invalid');
    });
}

requestSubmit()

// requestSubmit() triggers the submit event and can accept a submitter
const form = document.getElementById('registration-form');

// Submit with a specific button as the submitter
const submitBtn = document.getElementById('submit-btn');
form.requestSubmit(submitBtn);

// requestSubmit() also invokes the browser's built-in validation
// Unlike form.submit(), which does not trigger the submit event
form.requestSubmit();  // Triggers validation + the submit event

// Difference from form.submit()
form.submit();  // Does not trigger the submit event, no validation

checkValidity() and Built-in Validation

const form = document.getElementById('registration-form');

// Check the validity of the entire form
const isValid = form.checkValidity();
console.log('Form valid:', isValid);

// Check the validity of a specific field
const emailInput = form.elements.email;
console.log('Email valid:', emailInput.checkValidity());

// Get all invalid elements
const invalidElements = form.querySelectorAll(':invalid');
invalidElements.forEach(el => {
    console.log(`${el.name}: ${el.validationMessage}`);
});

// Get all valid elements
const validElements = form.querySelectorAll(':valid');

setCustomValidity()

const form = document.getElementById('registration-form');
const studentIdInput = form.elements.studentId;
const emailInput = form.elements.email;

// Set custom validity message
studentIdInput.addEventListener('input', () => {
    const value = studentIdInput.value;

    if (value.length === 0) {
        studentIdInput.setCustomValidity('StudentID is required');
    } else if (!/^\d{10}$/.test(value)) {
        studentIdInput.setCustomValidity('StudentID must be 10 digits');
    } else if (!value.startsWith('20')) {
        studentIdInput.setCustomValidity('StudentID must start with the enrollment year');
    } else {
        studentIdInput.setCustomValidity('');  // Reset = valid
    }

    updateFieldStatus(studentIdInput);
});

emailInput.addEventListener('input', () => {
    const value = emailInput.value;

    if (value.length === 0) {
        emailInput.setCustomValidity('Email is required');
    } else if (!value.includes('@')) {
        emailInput.setCustomValidity('Email must contain @');
    } else if (!value.endsWith('@university.edu')) {
        emailInput.setCustomValidity('Must use a university email (@university.edu)');
    } else {
        emailInput.setCustomValidity('');
    }

    updateFieldStatus(emailInput);
});

function updateFieldStatus(input) {
    const isValid = input.checkValidity();
    const feedback = input.nextElementSibling;

    input.classList.remove('is-valid', 'is-invalid');
    input.classList.add(isValid ? 'is-valid' : 'is-invalid');

    if (feedback && feedback.classList.contains('feedback')) {
        feedback.textContent = isValid ? '' : input.validationMessage;
    }
}

// Submit handler
form.addEventListener('submit', (event) => {
    event.preventDefault();

    if (!form.checkValidity()) {
        // Show all errors
        form.querySelectorAll(':invalid').forEach(input => {
            updateFieldStatus(input);
        });
        return;
    }

    // Form is valid, submit
    submitForm(new FormData(form));
});

Real-World Example: Graduation Registration Form

class GraduationRegistration {
    constructor() {
        this.form = document.getElementById('graduation-form');
        this.submitting = false;
        this.init();
    }

    init() {
        // Catch the submit
        this.form.addEventListener('submit', (e) => {
            e.preventDefault();
            this.handleSubmit();
        });

        // Live validation
        this.form.querySelectorAll('input, select, textarea').forEach(input => {
            input.addEventListener('input', () => {
                this.validateField(input);
                this.updateSubmitButton();
            });
        });

        // File upload handling
        this.form.querySelectorAll('input[type="file"]').forEach(input => {
            input.addEventListener('change', (e) => {
                this.handleFileUpload(e.target);
            });
        });
    }

    validateField(field) {
        const value = field.value.trim();
        let isValid = true;
        let message = '';

        switch (field.name) {
            case 'name':
                if (value.length < 3) {
                    isValid = false;
                    message = 'Name at least 3 characters';
                }
                break;

            case 'studentId':
                if (!/^\d{10}$/.test(value)) {
                    isValid = false;
                    message = 'StudentID must be 10 digits';
                }
                break;

            case 'email':
                if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                    isValid = false;
                    message = 'Invalid email format';
                }
                break;

            case 'gpa':
                const gpa = parseFloat(value);
                if (isNaN(gpa) || gpa < 0 || gpa > 4) {
                    isValid = false;
                    message = 'GPA must be between 0.00 - 4.00';
                } else if (gpa < 2.5) {
                    isValid = false;
                    message = 'GPA must be at least 2.50 for graduation';
                }
                break;

            case 'semester':
                const sem = parseInt(value);
                if (isNaN(sem) || sem < 6 || sem > 14) {
                    isValid = false;
                    message = 'Semester must be between 6 - 14';
                }
                break;

            case 'thesis_title':
                if (value.length < 10) {
                    isValid = false;
                    message = 'Thesis title at least 10 characters';
                } else if (value.length > 200) {
                    isValid = false;
                    message = 'Thesis title at most 200 characters';
                }
                break;
        }

        field.setCustomValidity(isValid ? '' : message);
        this.updateFieldUI(field, isValid, message);
        return isValid;
    }

    updateFieldUI(field, isValid, message) {
        const feedbackEl = field.parentElement.querySelector('.feedback');

        field.classList.remove('is-valid', 'is-invalid');
        field.classList.add(isValid ? 'is-valid' : 'is-invalid');

        if (feedbackEl) {
            feedbackEl.textContent = isValid ? '' : message;
            feedbackEl.classList.toggle('error', !isValid);
        }
    }

    handleFileUpload(input) {
        const file = input.files[0];
        if (!file) return;

        const maxSize = {
            'transcript': 2 * 1024 * 1024,    // 2MB
            'thesis': 5 * 1024 * 1024,       // 5MB
            'ktp': 1 * 1024 * 1024,           // 1MB
            'passportPhoto': 500 * 1024             // 500KB
        };

        const allowedTypes = {
            'transcript': ['application/pdf'],
            'thesis': ['application/pdf'],
            'ktp': ['image/jpeg', 'image/png'],
            'passportPhoto': ['image/jpeg', 'image/png']
        };

        const fieldName = input.name;
        const fileMaxSize = maxSize[fieldName] || 2 * 1024 * 1024;
        const fileAllowedTypes = allowedTypes[fieldName] || ['application/pdf'];

        let isValid = true;
        let message = '';

        if (file.size > fileMaxSize) {
            isValid = false;
            message = `Maximum file size ${this.formatSize(fileMaxSize)}`;
        } else if (!fileAllowedTypes.includes(file.type)) {
            isValid = false;
            message = `File format must be: ${fileAllowedTypes.join(', ')}`;
        }

        if (isValid) {
            this.showFilePreview(input, file);
        } else {
            this.showFileError(input, message);
            input.value = '';
        }
    }

    showFilePreview(input, file) {
        const container = input.parentElement;
        let preview = container.querySelector('.file-preview');

        if (!preview) {
            preview = document.createElement('div');
            preview.className = 'file-preview';
            container.appendChild(preview);
        }

        if (file.type.startsWith('image/')) {
            const reader = new FileReader();
            reader.onload = (e) => {
                preview.innerHTML = `
                    <img src="${e.target.result}" alt="Preview" style="max-width:200px">
                    <p>${file.name} (${this.formatSize(file.size)})</p>
                    <button type="button" class="remove-file" data-field="${input.name}">Delete</button>
                `;
            };
            reader.readAsDataURL(file);
        } else {
            preview.innerHTML = `
                <p>📄 ${file.name} (${this.formatSize(file.size)})</p>
                <button type="button" class="remove-file" data-field="${input.name}">Delete</button>
            `;
        }
    }

    showFileError(input, message) {
        const container = input.parentElement;
        let error = container.querySelector('.file-error');

        if (!error) {
            error = document.createElement('p');
            error.className = 'file-error error';
            container.appendChild(error);
        }

        error.textContent = message;
    }

    formatSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
    }

    updateSubmitButton() {
        const submitBtn = document.getElementById('submit-btn');
        const allValid = this.form.checkValidity();
        submitBtn.disabled = !allValid || this.submitting;
    }

    async handleSubmit() {
        if (this.submitting) return;

        // Validate all fields
        let allValid = true;
        this.form.querySelectorAll('[required]').forEach(field => {
            if (!this.validateField(field)) {
                allValid = false;
            }
        });

        if (!allValid) {
            this.showError('Please fill in all fields correctly');
            return;
        }

        this.submitting = true;
        this.updateSubmitButton();

        try {
            const formData = new FormData(this.form);

            // Send to the server
            const response = await fetch('/api/graduation/register', {
                method: 'POST',
                body: formData
            });

            const result = await response.json();

            if (result.success) {
                this.showSuccess('Graduation registration successful! Please check your email for confirmation');
                this.form.reset();
                document.querySelectorAll('.file-preview').forEach(el => el.remove());
            } else {
                this.showError(result.message || 'Failed to register for graduation');
            }
        } catch (error) {
            console.error('Error:', error);
            this.showError('A connection error occurred. Please try again.');
        } finally {
            this.submitting = false;
            this.updateSubmitButton();
        }
    }

    showError(message) {
        const container = document.getElementById('form-messages');
        container.innerHTML = `<div class="alert alert-error">${message}</div>`;
    }

    showSuccess(message) {
        const container = document.getElementById('form-messages');
        container.innerHTML = `<div class="alert alert-success">${message}</div>`;
    }
}

// Initialize
new GraduationRegistration();

Example: Multi-Step Form with Validation

class MultiStepForm {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.currentStep = 0;
        this.steps = this.form.querySelectorAll('.form-step');
        this.totalSteps = this.steps.length;
        this.data = {};

        this.init();
    }

    init() {
        // Navigation buttons
        this.form.querySelectorAll('.next-btn').forEach(btn => {
            btn.addEventListener('click', () => this.nextStep());
        });

        this.form.querySelectorAll('.prev-btn').forEach(btn => {
            btn.addEventListener('click', () => this.prevStep());
        });

        // Submit
        this.form.addEventListener('submit', (e) => {
            e.preventDefault();
            this.submitAll();
        });

        // Show first step
        this.steps[0].classList.add('active');
        this.updateProgress();
    }

    nextStep() {
        if (!this.validateCurrentStep()) return;

        this.saveCurrentStepData();
        this.steps[this.currentStep].classList.remove('active');
        this.currentStep++;
        this.steps[this.currentStep].classList.add('active');
        this.updateProgress();
    }

    prevStep() {
        this.saveCurrentStepData();
        this.steps[this.currentStep].classList.remove('active');
        this.currentStep--;
        this.steps[this.currentStep].classList.add('active');
        this.updateProgress();
    }

    validateCurrentStep() {
        const step = this.steps[this.currentStep];
        const inputs = step.querySelectorAll('input[required], select[required], textarea[required]');
        let valid = true;

        inputs.forEach(input => {
            if (!input.value.trim()) {
                input.classList.add('is-invalid');
                valid = false;
            } else {
                input.classList.remove('is-invalid');
            }

            if (!input.checkValidity()) {
                valid = false;
            }
        });

        if (!valid) {
            this.showError('Please fill in all required fields');
        }

        return valid;
    }

    saveCurrentStepData() {
        const step = this.steps[this.currentStep];
        const formData = new FormData(step.querySelector('form') || this.form);

        for (const [key, value] of formData) {
            this.data[key] = value;
        }
    }

    updateProgress() {
        const progress = ((this.currentStep + 1) / this.totalSteps) * 100;
        const progressBar = this.form.querySelector('.progress-fill');
        progressBar.style.width = `${progress}%`;

        // Update step indicators
        this.form.querySelectorAll('.step-indicator').forEach((ind, i) => {
            ind.classList.toggle('active', i === this.currentStep);
            ind.classList.toggle('completed', i < this.currentStep);
        });
    }

    async submitAll() {
        const allData = { ...this.data };

        try {
            const response = await fetch('/api/multi-step-submit', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(allData)
            });

            const result = await response.json();

            if (result.success) {
                this.showSuccess('Form successful disubmit!');
            }
        } catch (error) {
            this.showError('Failed to send data');
        }
    }

    showError(message) {
        const msg = this.form.querySelector('.form-message');
        msg.className = 'form-message error';
        msg.textContent = message;
        msg.style.display = 'block';
    }

    showSuccess(message) {
        const msg = this.form.querySelector('.form-message');
        msg.className = 'form-message success';
        msg.textContent = message;
        msg.style.display = 'block';
    }
}

// Usage
new MultiStepForm('registration-form');

Example: Lecturer Evaluation Form

class FacultyEvaluation {
    constructor() {
        this.form = document.getElementById('eval-form');
        this.ratings = {};
        this.init();
    }

    init() {
        // Catch all star ratings
        this.form.querySelectorAll('.star-rating').forEach(rating => {
            const stars = rating.querySelectorAll('.star');
            const fieldName = rating.dataset.field;

            stars.forEach((star, index) => {
                star.addEventListener('click', () => {
                    this.setRating(fieldName, index + 1);
                    this.updateStars(rating, index + 1);
                });

                star.addEventListener('mouseenter', () => {
                    this.previewStars(rating, index + 1);
                });
            });

            rating.addEventListener('mouseleave', () => {
                this.resetStars(rating, this.ratings[fieldName] || 0);
            });
        });

        // Catch the feedback textareas
        this.form.querySelectorAll('textarea').forEach(textarea => {
            textarea.addEventListener('input', () => {
                this.updateCharCount(textarea);
            });
        });

        // Submit
        this.form.addEventListener('submit', (e) => {
            e.preventDefault();
            this.submitEvaluation();
        });
    }

    setRating(field, value) {
        this.ratings[field] = value;
        this.updateSubmitButton();
    }

    updateStars(container, count) {
        container.querySelectorAll('.star').forEach((star, i) => {
            star.classList.toggle('active', i < count);
        });
    }

    previewStars(container, count) {
        container.querySelectorAll('.star').forEach((star, i) => {
            star.classList.toggle('hover', i < count);
        });
    }

    resetStars(container, count) {
        container.querySelectorAll('.star').forEach((star, i) => {
            star.classList.remove('hover');
            star.classList.toggle('active', i < count);
        });
    }

    updateCharCount(textarea) {
        const counter = textarea.parentElement.querySelector('.char-count');
        const remaining = 500 - textarea.value.length;
        counter.textContent = `${textarea.value.length}/500`;
        counter.classList.toggle('warning', remaining < 50);
        counter.classList.toggle('danger', remaining < 0);
    }

    updateSubmitButton() {
        const requiredRatings = ['clarity', 'mastery', 'responsive', 'attendance'];
        const allRated = requiredRatings.every(field => this.ratings[field]);
        this.form.querySelector('.submit-btn').disabled = !allRated;
    }

    async submitEvaluation() {
        const formData = {
            lecturerId: this.form.querySelector('[name="lecturer_id"]').value,
            course: this.form.querySelector('[name="course"]').value,
            ratings: this.ratings,
            feedback: {
                strengths: this.form.querySelector('[name="strengths"]').value,
                kesubtractan: this.form.querySelector('[name="kesubtractan"]').value,
                saran: this.form.querySelector('[name="saran"]').value
            },
            timestamp: new Date().toISOString()
        };

        try {
            const response = await fetch('/api/evaluation/submit', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(formData)
            });

            const result = await response.json();

            if (result.success) {
                this.showSuccess('Evaluation sent successfully! Thank you');
                this.form.reset();
                this.ratings = {};
                this.form.querySelectorAll('.star').forEach(s => s.classList.remove('active'));
            }
        } catch (error) {
            this.showError('Failed to send evaluation');
        }
    }
}

Example: Dynamic Form Builder

class FormBuilder {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.fields = [];
        this.formData = {};
    }

    addField(config) {
        const field = {
            id: config.id || `field-${this.fields.length}`,
            type: config.type || 'text',
            label: config.label || '',
            name: config.name || '',
            required: config.required || false,
            placeholder: config.placeholder || '',
            options: config.options || [],
            validators: config.validators || [],
            ...config
        };

        this.fields.push(field);
        this.renderField(field);
        return this;
    }

    renderField(field) {
        const wrapper = document.createElement('div');
        wrapper.className = 'form-field';
        wrapper.id = `wrapper-${field.id}`;

        let inputHTML = '';

        switch (field.type) {
            case 'text':
            case 'email':
            case 'number':
            case 'tel':
            case 'date':
                inputHTML = `
                    <input 
                        type="${field.type}" 
                        id="${field.id}" 
                        name="${field.name}"
                        placeholder="${field.placeholder}"
                        ${field.required ? 'required' : ''}
                        class="form-control"
                    >
                `;
                break;

            case 'textarea':
                inputHTML = `
                    <textarea 
                        id="${field.id}" 
                        name="${field.name}"
                        placeholder="${field.placeholder}"
                        ${field.required ? 'required' : ''}
                        class="form-control"
                        rows="4"
                    ></textarea>
                `;
                break;

            case 'select':
                inputHTML = `
                    <select id="${field.id}" name="${field.name}" class="form-control" ${field.required ? 'required' : ''}>
                        <option value="">${field.placeholder || 'Select...'}</option>
                        ${field.options.map(opt => `
                            <option value="${opt.value}">${opt.label}</option>
                        `).join('')}
                    </select>
                `;
                break;

            case 'radio':
                inputHTML = field.options.map(opt => `
                    <label class="radio-label">
                        <input type="radio" name="${field.name}" value="${opt.value}" ${field.required ? 'required' : ''}>
                        ${opt.label}
                    </label>
                `).join('');
                break;

            case 'checkbox':
                inputHTML = field.options.map(opt => `
                    <label class="checkbox-label">
                        <input type="checkbox" name="${field.name}" value="${opt.value}">
                        ${opt.label}
                    </label>
                `).join('');
                break;

            case 'file':
                inputHTML = `
                    <input type="file" id="${field.id}" name="${field.name}" 
                        ${field.accept ? `accept="${field.accept}"` : ''}
                        ${field.multiple ? 'multiple' : ''}
                        ${field.required ? 'required' : ''}>
                `;
                break;
        }

        wrapper.innerHTML = `
            <label for="${field.id}" class="form-label">
                ${field.label}
                ${field.required ? '<span class="required">*</span>' : ''}
            </label>
            ${inputHTML}
            <div class="feedback" id="feedback-${field.id}"></div>
        `;

        this.container.appendChild(wrapper);

        // Add validation
        const input = wrapper.querySelector('input, select, textarea');
        if (input) {
            input.addEventListener('input', () => {
                this.validateField(field, input.value);
            });
        }
    }

    validateField(field, value) {
        let isValid = true;
        let message = '';

        for (const validator of field.validators) {
            const result = validator(value);
            if (!result.valid) {
                isValid = false;
                message = result.message;
                break;
            }
        }

        const feedback = document.getElementById(`feedback-${field.id}`);
        const input = document.getElementById(field.id);

        input.classList.toggle('is-valid', isValid && value);
        input.classList.toggle('is-invalid', !isValid);
        feedback.textContent = !isValid ? message : '';

        return isValid;
    }

    getData() {
        const data = {};
        const formData = new FormData();

        this.fields.forEach(field => {
            const input = document.getElementById(field.id);
            if (input) {
                if (field.type === 'checkbox') {
                    const checked = document.querySelectorAll(`input[name="${field.name}"]:checked`);
                    data[field.name] = Array.from(checked).map(cb => cb.value);
                } else {
                    data[field.name] = input.value;
                }
            }
        });

        return data;
    }

    validateAll() {
        let allValid = true;

        this.fields.forEach(field => {
            if (field.required) {
                const input = document.getElementById(field.id);
                const value = input ? input.value : '';

                if (!value.trim()) {
                    allValid = false;
                    this.validateField(field, '');
                }
            }
        });

        return allValid;
    }
}

// Usage
const builder = new FormBuilder('dynamic-form');

builder
    .addField({
        type: 'text',
        label: 'Full name',
        name: 'name',
        required: true,
        placeholder: 'Enter your full name',
        validators: [
            (v) => ({ valid: v.length >= 3, message: 'Name at least 3 characters' })
        ]
    })
    .addField({
        type: 'email',
        label: 'Email',
        name: 'email',
        required: true,
        placeholder: '[email protected]',
        validators: [
            (v) => ({ valid: v.includes('@'), message: 'Invalid email' })
        ]
    })
    .addField({
        type: 'select',
        label: 'Major',
        name: 'majorCode',
        required: true,
        options: [
            { value: 'tif', label: 'Computer Science' },
            { value: 'si', label: 'Information Systems' },
            { value: 'ce', label: 'Computer Engineering' }
        ]
    })
    .addField({
        type: 'textarea',
        label: 'Motivasi',
        name: 'motivasi',
        required: true,
        placeholder: 'Tell us about your motivation...',
        validators: [
            (v) => ({ valid: v.length >= 50, message: 'At least 50 characters' })
        ]
    });

Code Explanation

FormData Methods

  • formData.get(name) - Get the value of the first field with a given name
  • formData.getAll(name) - Get all values (for checkboxes/radios)
  • formData.set(name, value) - Set/replace a field's value
  • formData.append(name, value) - Add a new field
  • formData.delete(name) - Remove a field
  • formData.has(name) - Check whether a field exists
  • formData.entries() - Iterator over all key-value pairs

Validation

  • form.checkValidity() - Check the validity of the entire form
  • input.checkValidity() - Check the validity of a single field
  • input.setCustomValidity(message) - Set a custom error message
  • input.validationMessage - The validation message displayed by the browser
  • CSS pseudo-classes :valid and :invalid for styling

Submit Methods

  • form.submit() - Submit without validation and without the event
  • form.requestSubmit() - Submit with validation and triggers the submit event
  • event.preventDefault() - Prevent the default submit in the event handler

Conclusion

The Forms API provides comprehensive tools for handling forms in JavaScript. By mastering FormData, validation, and custom validation, you can build robust form handling for a campus application. For example, in a graduation registration form, combining the browser's built-in validation with custom validation ensures that incoming data is valid and complete. Always use preventDefault() in your submit handler to control the submission process, and leverage FormData to submit data that includes file uploads.