Back

Manejo de Formularios con JavaScript Vanilla: Sin Frameworks Requeridos

Manejo de Formularios con JavaScript Vanilla: Sin Frameworks Requeridos

Los formularios son la columna vertebral de la interacción del usuario en la web. Aunque frameworks como React y Vue ofrecen abstracciones convenientes, entender cómo manejar formularios con JavaScript vanilla te brinda control completo y elimina dependencias innecesarias.

Esta guía te lleva a través de todo lo que necesitas saber sobre el manejo nativo de formularios en JavaScript—desde seleccionar elementos y capturar valores hasta validar entrada y procesar envíos.

Puntos Clave

  • JavaScript vanilla proporciona todas las herramientas necesarias para el manejo completo de formularios sin frameworks
  • Usa event.preventDefault() para controlar el comportamiento de envío del formulario
  • Aprovecha la validación de restricciones HTML5 para validación básica con código mínimo
  • Implementa validación en tiempo real con los eventos input y blur para una mejor experiencia de usuario
  • La API FormData simplifica la recolección y procesamiento de valores de formulario
  • Siempre considera la accesibilidad al implementar validación personalizada de formularios

Manejo Básico de Formularios con JavaScript

Comencemos con un formulario HTML simple:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password">
  </div>
  <button type="submit">Sign Up</button>
</form>

Selección de Elementos del Formulario

Puedes acceder a un formulario y sus elementos de varias maneras:

// Seleccionar por ID (recomendado para formularios específicos)
const form = document.getElementById('signup-form');

// Seleccionar usando querySelector
const form = document.querySelector('#signup-form');

// Acceder a la colección de formularios
const forms = document.forms;
const signupForm = forms['signup-form']; // por id/name
const firstForm = forms[0]; // por índice

Capturar el Evento Submit

Para procesar el envío del formulario, agrega un event listener al formulario:

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

form.addEventListener('submit', function(event) {
  // Prevenir el envío predeterminado del formulario
  event.preventDefault();
  
  // Procesar datos del formulario aquí
  console.log('Form submitted!');
});

El método preventDefault() evita que el navegador envíe el formulario y actualice la página, dándote control sobre lo que sucede después.

Acceso a Valores de Entrada del Formulario

Hay múltiples formas de acceder a los valores de entrada del formulario:

Método 1: Acceder a Elementos Individuales

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // Obtener valores de elementos individuales del formulario
  const username = document.getElementById('username').value;
  const email = document.getElementById('email').value;
  const password = document.getElementById('password').value;
  
  console.log(username, email, password);
});

Método 2: Usando form.elements

La propiedad elements proporciona acceso a todos los controles del formulario:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // Acceder vía colección elements
  const username = form.elements.username.value;
  const email = form.elements.email.value;
  const password = form.elements.password.value;
  
  console.log(username, email, password);
});

Método 3: Usando la API FormData

La API FormData proporciona una forma limpia de capturar todos los valores del formulario:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // Crear objeto FormData desde el formulario
  const formData = new FormData(form);
  
  // Acceder a valores individuales
  const username = formData.get('username');
  const email = formData.get('email');
  
  // Convertir a un objeto regular
  const formObject = Object.fromEntries(formData);
  console.log(formObject); // {username: '...', email: '...', password: '...'}
});

Validación de Formularios con Restricciones HTML5

HTML5 moderno proporciona atributos de validación integrados que funcionan sin JavaScript:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required minlength="3">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required minlength="8" 
           pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$">
    <small>Password must contain at least 8 characters, including uppercase, lowercase, and numbers</small>
  </div>
  <button type="submit">Sign Up</button>
</form>

Los atributos de validación comunes incluyen:

  • required: El campo debe ser completado
  • minlength/maxlength: Longitud mínima/máxima del texto
  • min/max: Valores mínimo/máximo para entradas numéricas
  • pattern: Patrón de expresión regular a coincidir
  • type="email": Valida formato de email
  • type="url": Valida formato de URL

Aprovechando la API de Validación de Restricciones

JavaScript puede acceder al sistema de validación integrado del navegador:

form.addEventListener('submit', function(event) {
  // Verificar si el formulario es válido usando la API de Validación de Restricciones
  if (!form.checkValidity()) {
    // El formulario es inválido - dejar que el navegador maneje mostrar errores
    return;
  }
  
  // Si llegamos aquí, el formulario es válido
  event.preventDefault();
  
  // Procesar los datos válidos del formulario
  console.log('Form is valid, processing data...');
});

También puedes verificar la validez de entradas específicas:

const emailInput = document.getElementById('email');

// Verificar si el email es válido
if (emailInput.validity.valid) {
  console.log('Email is valid');
} else {
  // Verificar fallas de validación específicas
  if (emailInput.validity.typeMismatch) {
    console.log('Email format is incorrect');
  }
  if (emailInput.validity.valueMissing) {
    console.log('Email is required');
  }
}

Validación Personalizada y Mensajes de Error

Aunque la validación HTML5 es conveniente, a menudo necesitas lógica de validación personalizada y mensajes de error:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  const username = document.getElementById('username');
  const email = document.getElementById('email');
  const password = document.getElementById('password');
  
  // Limpiar mensajes de error anteriores
  clearErrors();
  
  let isValid = true;
  
  // Validación personalizada de nombre de usuario
  if (username.value.trim() === '') {
    displayError(username, 'Username is required');
    isValid = false;
  } else if (username.value.length < 3) {
    displayError(username, 'Username must be at least 3 characters');
    isValid = false;
  }
  
  // Validación personalizada de email
  if (!isValidEmail(email.value)) {
    displayError(email, 'Please enter a valid email address');
    isValid = false;
  }
  
  // Validación personalizada de contraseña
  if (password.value.length < 8) {
    displayError(password, 'Password must be at least 8 characters');
    isValid = false;
  } else if (!/[A-Z]/.test(password.value)) {
    displayError(password, 'Password must contain at least one uppercase letter');
    isValid = false;
  }
  
  // Si es válido, procesar el formulario
  if (isValid) {
    console.log('Form is valid, submitting...');
    // Enviar datos del formulario
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorElement = document.createElement('div');
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  formControl.appendChild(errorElement);
  input.classList.add('error-input');
}

function clearErrors() {
  document.querySelectorAll('.error-message').forEach(error => error.remove());
  document.querySelectorAll('.error-input').forEach(input => {
    input.classList.remove('error-input');
  });
}

Validación en Tiempo Real con Eventos de Entrada

Para una mejor experiencia de usuario, valida la entrada mientras los usuarios escriben:

const emailInput = document.getElementById('email');

emailInput.addEventListener('input', function() {
  if (this.value && !isValidEmail(this.value)) {
    this.setCustomValidity('Please enter a valid email address');
  } else {
    this.setCustomValidity('');
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

El evento input se dispara cada vez que el usuario cambia el valor, mientras que el evento change se dispara cuando la entrada pierde el foco después de haber sido cambiada.

Manejo de Diferentes Tipos de Entrada

Botones de Radio

<div>
  <p>Subscription plan:</p>
  <label>
    <input type="radio" name="plan" value="free" checked> Free
  </label>
  <label>
    <input type="radio" name="plan" value="premium"> Premium
  </label>
</div>
// Obtener valor del botón de radio seleccionado
const selectedPlan = document.querySelector('input[name="plan"]:checked').value;

// Escuchar cambios
document.querySelectorAll('input[name="plan"]').forEach(radio => {
  radio.addEventListener('change', function() {
    console.log('Selected plan:', this.value);
  });
});

Casillas de Verificación

<div>
  <p>Interests:</p>
  <label>
    <input type="checkbox" name="interests" value="javascript"> JavaScript
  </label>
  <label>
    <input type="checkbox" name="interests" value="html"> HTML
  </label>
  <label>
    <input type="checkbox" name="interests" value="css"> CSS
  </label>
</div>
// Obtener todos los valores marcados
function getSelectedInterests() {
  const checkboxes = document.querySelectorAll('input[name="interests"]:checked');
  const values = Array.from(checkboxes).map(cb => cb.value);
  return values;
}

// En el envío del formulario
form.addEventListener('submit', function(event) {
  event.preventDefault();
  const interests = getSelectedInterests();
  console.log('Selected interests:', interests);
});

Menús Desplegables Select

<div>
  <label for="country">Country:</label>
  <select id="country" name="country">
    <option value="">Select a country</option>
    <option value="us">United States</option>
    <option value="ca">Canada</option>
    <option value="uk">United Kingdom</option>
  </select>
</div>
// Obtener valor seleccionado
const country = document.getElementById('country').value;

// Escuchar cambios
document.getElementById('country').addEventListener('change', function() {
  console.log('Selected country:', this.value);
});

Ejemplo Completo de Procesamiento de Formularios

Pongamos todo junto en un ejemplo completo:

document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('signup-form');
  
  // Agregar validación en tiempo real
  setupRealTimeValidation();
  
  form.addEventListener('submit', function(event) {
    event.preventDefault();
    
    if (validateForm()) {
      // Crear objeto con datos del formulario
      const formData = new FormData(form);
      const userData = Object.fromEntries(formData);
      
      // Aquí típicamente enviarías los datos a un servidor
      console.log('Submitting user data:', userData);
      
      // Simular llamada API
      submitFormData(userData)
        .then(response => {
          showSuccessMessage('Account created successfully!');
          form.reset();
        })
        .catch(error => {
          showErrorMessage('Failed to create account. Please try again.');
        });
    }
  });
  
  function validateForm() {
    // Limpiar errores anteriores
    clearErrors();
    
    let isValid = true;
    const username = form.elements.username;
    const email = form.elements.email;
    const password = form.elements.password;
    
    // Validar nombre de usuario
    if (username.value.trim() === '') {
      displayError(username, 'Username is required');
      isValid = false;
    } else if (username.value.length < 3) {
      displayError(username, 'Username must be at least 3 characters');
      isValid = false;
    }
    
    // Validar email
    if (email.value.trim() === '') {
      displayError(email, 'Email is required');
      isValid = false;
    } else if (!isValidEmail(email.value)) {
      displayError(email, 'Please enter a valid email address');
      isValid = false;
    }
    
    // Validar contraseña
    if (password.value === '') {
      displayError(password, 'Password is required');
      isValid = false;
    } else if (password.value.length < 8) {
      displayError(password, 'Password must be at least 8 characters');
      isValid = false;
    } else if (!/[A-Z]/.test(password.value) || !/[a-z]/.test(password.value) || !/[0-9]/.test(password.value)) {
      displayError(password, 'Password must include uppercase, lowercase, and numbers');
      isValid = false;
    }
    
    return isValid;
  }
  
  function setupRealTimeValidation() {
    const inputs = form.querySelectorAll('input');
    
    inputs.forEach(input => {
      input.addEventListener('input', function() {
        // Limpiar error cuando el usuario comienza a escribir
        const errorElement = this.parentElement.querySelector('.error-message');
        if (errorElement) {
          errorElement.remove();
          this.classList.remove('error-input');
        }
      });
    });
    
    // Validación específica de email
    form.elements.email.addEventListener('blur', function() {
      if (this.value && !isValidEmail(this.value)) {
        displayError(this, 'Please enter a valid email address');
      }
    });
  }
  
  // Funciones auxiliares
  function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  function displayError(input, message) {
    const formControl = input.parentElement;
    const errorElement = document.createElement('div');
    errorElement.className = 'error-message';
    errorElement.textContent = message;
    formControl.appendChild(errorElement);
    input.classList.add('error-input');
  }
  
  function clearErrors() {
    document.querySelectorAll('.error-message').forEach(error => error.remove());
    document.querySelectorAll('.error-input').forEach(input => {
      input.classList.remove('error-input');
    });
  }
  
  function showSuccessMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'success-message';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // Remover después de 3 segundos
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  function showErrorMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'error-banner';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // Remover después de 3 segundos
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  // Simular envío API
  function submitFormData(data) {
    return new Promise((resolve, reject) => {
      // Simular petición de red
      setTimeout(() => {
        // 90% de tasa de éxito para demo
        if (Math.random() > 0.1) {
          resolve({ success: true, message: 'User created' });
        } else {
          reject({ success: false, message: 'Server error' });
        }
      }, 1500);
    });
  }
});

Consideraciones de Accesibilidad para Formularios

Al manejar formularios con JavaScript, asegúrate de que permanezcan accesibles:

  1. Mantener gestión de foco: Al mostrar errores, establecer foco en el primer campo inválido
  2. Usar atributos ARIA: Agregar aria-invalid="true" a campos inválidos
  3. Asociar mensajes de error: Usar aria-describedby para conectar entradas con sus mensajes de error
  4. Proporcionar instrucciones claras: Usar elementos <label> y mensajes de error descriptivos
  5. Soportar navegación por teclado: Asegurar que todos los controles del formulario sean accesibles por teclado

Ejemplo de manejo de errores accesible:

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorId = `${input.id}-error`;
  
  const errorElement = document.createElement('div');
  errorElement.id = errorId;
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  
  formControl.appendChild(errorElement);
  
  // Agregar atributos de accesibilidad
  input.setAttribute('aria-invalid', 'true');
  input.setAttribute('aria-describedby', errorId);
  
  // Establecer foco en la primera entrada inválida
  if (!document.querySelector('.error-input')) {
    input.focus();
  }
  
  input.classList.add('error-input');
}

Preguntas Frecuentes

Usa el método reset integrado en el elemento del formulario: document.getElementById('myForm').reset();

Usa el método checkValidity de la API de Validación de Restricciones: const isValid = form.checkValidity();

Para enviar datos de formulario de forma asíncrona, usa la API Fetch junto con el objeto FormData. Primero, crea una instancia FormData desde tu formulario. Luego envíala usando fetch con el método POST. Asegúrate de manejar la respuesta y capturar cualquier error durante el envío.

Para manejar la carga de archivos, accede a la propiedad files del elemento input de archivo, recupera el archivo seleccionado, y agrégalo a un objeto FormData antes de enviarlo usando fetch u otro método.

El evento `input` se dispara cada vez que el valor de entrada cambia, mientras que el evento `change` solo se dispara cuando la entrada pierde el foco y el valor ha cambiado desde que obtuvo el foco.

Para agregar campos de formulario dinámicamente, crea nuevos elementos input usando document.createElement, establece su tipo y nombre, y agrégalos a un elemento contenedor en el formulario.

Conclusión

Al dominar el manejo de formularios con JavaScript vanilla, obtienes control completo sobre tus formularios sin depender de librerías externas. Este enfoque reduce dependencias, mejora el rendimiento y profundiza tu comprensión de cómo funcionan realmente los formularios web.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers