Validación de formulario de datos

En tanto tú puedes siempre validar datos en tu servidor, el tener validación adicional de datos en la página web tiene múltiples beneficios. En muchas formas, a los usuarios les molestan los formularios. Si validamos los datos de un formulario mientras el usuario lo llena, el usuario puede saber inmediatamente si ha cometido algún error; esto le ahorra tiempo de espera a una respuesta HTTP y le evita al servidor lidiar con entradas incorrectas en el formulario. Este artículo cubre la validación de datos de formulario dentro del contenido Web de un formulario.

Usando validación de formularios proporcionada por el navegador

Una de las características de HTML5 es la habilidad de validar la mayoría de datos del usuario sin depender de scripts. Esto se hace usando atributos de validación en elementos de formulario.

Cuando un elemento es inválido

Cuando un elemento es inválido, ocurren dos cosas:

  • El elemento coincide con la pseudo-clase de CSS :invalid; esto te permitirá aplicar estilos específicos a elementos inválidos. De forma similar, los elementos válidos coinciden con la pseudo-clase :valid.
  • Si el usuario intenta enviar los datos, el navegador bloqueará al formulario y mostrará un mensaje de error.

Restricciones de validación en elementos <input>

Todos los elemento <input> pueden ser validados usando el atributo pattern. Este atributo espera como valor una Expresión Regular, sensible a uso de mayúsculas. Si el valor del elemento no es vacío y no coincide con la expresión regular especificada en el atributo pattern, el elemento es considerado inválido.

Un ejemplo simple

<form>
  <label for="choose">¿Preferirías un plátano o una cereza?</label>
  <input id="choose" name="i_like" pattern="plátano|cereza">
  <button>Enviar</button>
</form>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

En este ejemplo, el elemento <input> acepta uno de tres valores posibles: una cadena vacía, el texto "plátano" o el texto "cereza".

El atributo required

Si un elemento requiere un valor antes de que el formulario sea enviado, puedes marcar al elemento usando el atributo required. Cuando este atributo es verdadero, no se permite que el campo esté vacío.

<form>
  <label for="choose">¿Preferirías un plátano o una cereza?</label>
  <input id="choose" name="i_like" pattern="plátano|cereza" required>
  <button>Enviar</button>
</form>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

Nota cómo el borde del campo es diferente del ejemplo anterior.

Nota: Los elementos <input> que tengan el atributo type con valor emailurl no necesitan un atributo pattern para ser validados. Si se especifica el tipo email, es necesario que el valor del campo sea una dirección bien formada de correo electrónico (o una lista de direcciones separadas por coma, si tiene el atributo multiple). Los campos con tipo url automáticamente necesitan una URL debidamente formada.

Otras restricciones de validación

Todos los tipos de elemento que pueden recibir entrada del usuario (<textarea>, <select>, etc.) soportan el atributo required, pero cabe aclarar que el elemento <textarea> no soporta el atributo pattern.

Todos los campos de texto (<input> or <textarea>) pueden ser restringidos en tamaño usando el atributo maxlength. Un campo es inválido si su valor es mayor al valor del atributo maxlength. La mayoría de las ocasiones, sin embargo, los navegadores no permiten que el usuario escriba un valor más largo del indicado.

Para campos numéricos, los atributos minmax también proveen una restricción de validación. Si el valor del campo es menor que el del atributo min o mayor que el del atributo max, el campo será inválido.

Aquí hay un ejemplo completo:

<form>
  <p>
    <fieldset>
      <legend>Title<abbr title="This field is mandatory">*</abbr></legend>
      <input type="radio" required name="title" id="r1" value="Sr" ><label for="r1">Sr. </label>
      <input type="radio" required name="title" id="r2" value="Sra"><label for="r2">Sra.</label>
    </fieldset>
  </p>
  <p>
    <label for="n1">¿Cuál es tu edad?</label>
    <!-- El atributo pattern puede actuar como fallback para navegadores que no implementen el tipo number pero sí soporten el atributo pattern.
         Nótese que los navegadores que soportan el atributo pattern lo harán fallar silenciosamente cuando se use con un campo numérico.
         Su uso aquí solo es como fallback -->
    <input type="number" min="12" max="120" step="1" id="n1" name="age"
           pattern="\d+">
  </p>
  <p>
    <label for="t1">¿Cuál es tu fruta favorita?<abbr title="Este campo es obligatorio">*</abbr></label>
    <input type="text" id="t1" name="fruit" list="l1" required
           pattern="[Pp]l[áa]tano|[Cc]ereza|[Mm]anzana|[Ff]resa|[Ll]im[oó]n|[Nn]aranja">
    <datalist id="l1">
        <option>Plátano</option>
        <option>Cereza</option>
        <option>Manzana</option>
        <option>Fresa</option>
        <option>Limón</option>
        <option>Naranja</option>
    </datalist>
  </p>
  <p>
    <label for="t2">¿Cuál es tu dirección de correo electrónico?</label>
    <input type="email" id="t2" name="email">
  </p>
  <p>
    <label for="t3">Déjanos un mensaje corto</label>
    <textarea id="t3" name="msg" maxlength="140" rows="5"></textarea>
  </p>
  <p>
    <button>Enviar</button>
  </p>
</form>
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
  margin: 0;
  padding: 0 5px;
}

p > label {
  display: block;
}

input[type=text],
input[type=email],
input[type=number],
textarea,
fieldset {
/* requerido para estilizar adecuadamente elementos
   de formulario en navegadores basados en WebKit */
  -webkit-appearance: none;
  
  width : 100%;
  border: 1px solid #333;
  margin: 0;
  
  font-family: inherit;
  font-size: 90%;
  
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

input:invalid {
  box-shadow: 0 0 5px 1px red;
}

input:focus:invalid {
  outline: none;
}

Mensajes de error personalizados

Como vimos en los ejemplos anteriores, cada vez que el usuario intenta enviar un formulario inválido, el navegador muestra un mensaje de error. La manera en que estos mensajes son mostrados depende del navegador.

Estos mensajes automatizados tienen dos inconvenientes:

  • No hay manera estándar de cambiar su apariencia con CSS.
  • Dependen de la localización del navegador, lo que significa que puedes tener una página en un lenguaje específico, pero el mensaje de error se puede en otro lenguaje.
Versión en francés del navegador en una página en inglés
Navegador Representación
Firefox 17 (Windows 7) Example of an error message with Firefox in French on an English page
Chrome 22 (Windows 7) Example of an error message with Chrome in French on an English page
Opera 12.10 (Mac OSX) Example of an error message with Opera in French on an English page

Para personalizar la apariencia y texto de estos mensajes, debes usar JavaScript; no hay manera de hacer esto usando solamente HTML y CSS.

HTML5 proporciona la API de validación de restricciones para verificar y personalizar los estados de un elemento de formulario. Entre otras cosas, es posible cambiar el texto de un mensaje de error. Veamos un ejemplo rápido:

<form>
  <label for="mail">Quisiera que me proporcionaras tu dirección de correo electrónico</label>
  <input type="email" id="mail" name="mail">
  <button>Enviar</button>
</form>

En JavaScript, invocas al método setCustomValidity():

var email = document.getElementById("mail");

email.addEventListener("keyup", function (event) {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("¡Yo esperaba una dirección de correo, cariño!");
  } else {
    email.setCustomValidity("");
  }
});

Validando formularios usando JavaScript

Si quieres tomar el control de la apariencia de un mensaje de error nativo, o si quieres lidiar con navegadores que no soportan validación de formularios de HTML5, no hay otra forma que con JavaScript.

La API de validación de restricciones de HTML5

Más y más navegadores ya soportan la API de validación de restricciones, por lo que se está volviendo más confiable. Esta API consiste de un conjunto de métodos y propiedades disponibles para cada elemento de formulario.

Propiedades de la API de validación de restricciones

Propiedad Descripción
validationMessage

Un mensaje en el idioma del navegador, que describe la restricción de validación que el control no satisface (si la hay), o el texto vacío si el control no es candidato a restricción de validación (willValidate es false), o el valor del elemento satisface las restricciones.

validity Un objeto ValidityState que describe el estado de validación del elemento.
validity.customError Devuelve true si el elemento tiene un error personalizado; de lo contrario, devuelve false.
validity.patternMismatch Devuelve true si el valor del elemento no cumple el patrón indicado; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con la pseudo-clase de CSS :invalid.
validity.rangeOverflow Devuelve true si el valor del elemento es mayor al máximo permitido; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con las pseudo-clases de CSS :invalid y :out-of-range.
validity.rangeUnderflow Devuelve true si el valor del elemento ese menor al mínimo permitido; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con las pseudo-clases de CSS :invalid y :out-of-range.
validity.stepMismatch Devuelve true si el valor del elemento no cumple las reglas indicadas por el atributo step; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con las pseudo-clases de CSS :invalid y :out-of-range.
validity.tooLong Devuelve true si el valor del elemento es mayor al indicado como longitud máxima; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con las pseudo-clases de CSS :invalid y :out-of-range.
validity.typeMismatch Devuelve true si el valor del elemento no tiene la sintaxis correcta; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con la pseudo-clase de CSS :invalid.
validity.valid Devuelve true si el valor del elemento no tiene problemas de validación; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con la pseudo-clase de CSS :valid; de lo contrario, coincide con la pseudo-clase :invalid.
validity.valueMissing Devuelve true si el elemento no tiene valor pero es un campo requerido; de lo contrario, devuelve false.

Si devuelve true, el elemento coincide con la pseudo-clase de CSS :invalid.
willValidate Devuelve true si el elemento será validado cuando el formulario es enviado; de lo contrario, devuelve false.

Métodos de la API de validación de restricciones

Método Descripción
checkValidity() Devuelve true si el valor del elemento no tiene problemas de validación; de lo contrario, devuelve false. Si el elemento es inválido, este método invocará a un evento invalid en el elemento.
setCustomValidity(message) Añade un mensaje de error personalizado al elemento; si estableces un mensaje de error personalizado, el elemento es considerado inválido, y se despliega el error especificado. Esto te permite usar código de JavaScript para establecer otras fallas de validación más allá de las ofrecidas por el estándar. El mensaje es mostrado al usuario cuando se reporta el problema.

Si el argumento es una cadena de texto vacía, el error personalizado se borra.

Para navegadores antiguos, es posible usar un polyfill como H5F para compensar la falta de soporte a la API de validación de restricciones. Si ya estamos usando JavaScript, usar un polyfill no es una carga añadida al diseño o implementación del sitio o aplicación Web.

Ejemplo usando la API de validación de restricciones

Veamos cómo usar esta API para construir mensajes de error personalizados. Primero, el HTML:

<form novalidate>
  <p>
    <label for="mail">
      <span>Por favor, introduzca una dirección de correo:</span>
      <input type="email" id="mail" name="mail">
      <span class="error" aria-live="polite"></span>
    </label>
  </p>
  <button>Enviar</button>
</form>

Este formulario simple usa el atributo novalidate para apagar la validación automática del navegador; esto le permite a nuestro script tomar el control sobre la validación. Sin embargo, esto no deshabilita el soporte a la API de validación de restricciones, ni a la aplicación de las pseudo-clases de CSS :valid, :invalid, :in-range y :out-of-range. Esto significa que, aunque el navegador no revise automáticamente la validez del formulario antes de enviar sus datos, aun puedes hacerlo tú mismo, y aplicar estilos según corresponda.

El atributo aria-live se asegura que nuestro mensaje de error personalizado será presentado a cualquiera, incluyendo a quienes usan tecnologías asistivas, como lectores de pantalla.

CSS

Este CSS estiliza nuestro formulario y el mensaje de error para que se vea más atractivo.

/* Esto sólo es para hacer que el ejemplo se vea mejor */
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
}

p * {
  display: block;
}

input[type=email]{
  -webkit-appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

/* Estos son los estilos para nuestros campos inválidos */
input:invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus:invalid {
  outline: none;
}

/* Estos son los estilos para nuestros mensajes de error */
.error {
  width  : 100%;
  padding: 0;
 
  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

El siguiente código de JavaScript maneja la validación de errores personalizados.

// Hay distintas formas de seleccionar un nodo del DOM; aquí obtenemos al formulario
// y al campo del correo, así como el elemento span dentro del cual pondremos el
// mensaje de error.

var form  = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');
var error = document.querySelector('.error');

email.addEventListener("keyup", function (event) {
  // Cada vez que el usuario escriba algo, revisaremos si
  // el campo de email es válido.
  if (email.validity.valid) {
    // En caso que haya un mensaje de error visible, si el campo
    // es válido, removemos el mensaje de error.
    error.innerHTML = ""; // Limpia el contenido del mensaje
    error.className = "error"; // Restablece el estado visual del mensaje
  }
}, false);
form.addEventListener("submit", function (event) {
  // Cada vez que el usuario intenta enviar los datos, verificamos
  // si el campo de correo es válido.
  if (!email.validity.valid) {
    
    // Si el campo no es válido, mostramos un mensaje de error.
    error.innerHTML = "¡Yo esperaba una dirección de correo, cariño!";
    error.className = "error active";
    // Y prevenimos que el formulario sea enviado, cancelando el evento
    event.preventDefault();
  }
}, false);

Aquí está el resultado en vivo:

La API de validación de restricciones nos da una herramienta poderosa para manejo de validaciones de formulario, permitiéndonos tener enorme control sobre la interfaz de usuario por encima y más allá de lo que se puede hacer con HTML y CSS.

Validando formularios sin una API incorporada

A veces, como en el caso de navegadores antiguos o widgets personalizados, no podrás (o no querrás) usar la API de validación de restricciones. En este caso, aun puedes usar JavaScript para validar tu formulario. Validar un formulario es más una cuestión de interfaz de usuario que de validación de datos real.

Para validar un formulario, debes hacerte unas cuantas preguntas:

¿Qué clase de validación debo realizar?
Necesitas determinar cómo validar tus datos: operaciones de texto, conversión de tipos, expresiones regulares, etc. Depende de ti. Solo recuerda que los datos de formulario siempre son texto, y siempre serán provistos a tu script como tal.
¿Qué debo hacer si el formulario no es válido?
Esto es claramente un asunto de UI. Debes decidir cómo se comportará el formulario: ¿Se deben enviar los datos de cualquier modo? ¿Se deben resaltar los campos que tienen error? ¿Se debe mostrar el mensaje de error?
¿Cómo puedo ayudar al usuario a corregir datos inválidos?
Para reducir las frustraciones de los usuarios, es muy importante proveer tanta información útil como sea posible para guiarlos en la corrección de sus datos. Debes ofrecer sugerencias que les indiquen qué es lo que se espera, así como mensajes de error claros.

Si quieres profundizar en requerimientos de UI de validación de formularios, hay algunos artículos útiles que deberías leer (todos en inglés):

Ejemplo que no usa la API de validación de restricciones

Para ilustrar esto, volvamos a construir el ejemplo anterior para que funcione en navegadores antiguos:

<form>
  <p>
    <label for="mail">
        <span>Por favor, introduzca una dirección de correo electrónico:</span>
        <input type="text" class="mail" id="mail" name="mail">
        <span class="error" aria-live="polite"></span>
    </label>
  <p>
  <!-- Algunos navegadores antiguos necesitan que se establezca explícitamente el atributo `type` con valor `submit` en el elemento `button` -->
  <button type="submit">Enviar</button>
</form>

Como puedes ver, el código HTML es casi el mismo; solo removimos las partes de HTML5. Nótese que ARIA es una especificación independiente, que no está específicamente relacionada con HTML5, así que podemos dejarla.

CSS

De forma similar, el código CSS no necesita cambiar mucho; solo cambiamos la pseudo-clase :invalid a una clase real, evitando usar atributos de selector que no funcionen en Internet Explorer 6.

/* Esto sólo es para hacer que el ejemplo se vea mejor */
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
}

p * {
  display: block;
}

input.mail {
  -webkit-appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

/* Estos son los estilos para nuestros campos inválidos */
input.invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus.invalid {
  outline: none;
}

/* Estos son los estilos para nuestros mensajes de error */
.error {
  width  : 100%;
  padding: 0;
 
  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

Los cambios grandes son en el código de JavaScript, que necesitan hacer mucho más del trabajo pesado.

// Hay pocas formas de seleccionar nodos del DOM en navegadores antiguos
var form  = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');

// Lo siguiente es un truco para obtener el siguiente elemento adyacente en el DOM
// Esto es peligroso porque podrías fácilmente provocar un bucle infinito
// En navegadores modernos, es preferible usar element.nextElementSibling
var error = email;
while ((error = error.nextSibling).nodeType != 1);

// Según la especificación HTML5
var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

// Muchos navegadores antiguos no soportan el método addEventListener.
// Aquí hay una forma simple de tratar con esto; no es la única.
function addEvent(element, event, callback) {
  var previousEventCallBack = element["on"+event];
  element["on"+event] = function (e) {
    var output = callback(e);
    
    // Un callback que devuelve `false` detiene la cadena de callbacks
    // e interrumpe la ejecución de las funciones del evento.
    if (output === false) return false;

    if (typeof previousEventCallBack === 'function') {
      output = previousEventCallBack(e);
      if(output === false) return false;
    }
  }
};

// Ahora podemos reconstruir nuestras restricciones de validaciones
// Como no podemos contar con pseudo-clases CSS, tenemos que establecer
// explícitamente la clase valid/invalid en nuestro campo de correo
addEvent(window, "load", function () {
  // Aquí, revisamos si el campo está vacío (recuerda, el campo no es requerido)
  // Si no lo es, revisamos si su contenido es una dirección de correo bien formada.
  var test = email.value.length === 0 || emailRegExp.test(email.value);
 
  email.className = test ? "valid" : "invalid";
});

// Esto define qué pasará cuando el usuario escribe en el campo de texto
addEvent(email, "keyup", function () {
  var test = email.value.length === 0 || emailRegExp.test(email.value);
  if (test) {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  } else {
    email.className = "invalid";
  }
});

// Esto define qué pasará cuando el usuario intente enviar los datos del formulario
addEvent(form, "submit", function () {
  var test = email.value.length === 0 || emailRegExp.test(email.value);
 
  if (!test) {
    email.className = "invalid";
    error.innerHTML = "I expect an e-mail, darling!";
    error.className = "error active";

    // Algunos navegadores antiguos no soportan el método event.preventDefault()
    return false;
  } else {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  }
});

El resultado se verá así:

Como puedes ver, no es tan difícil construir un sistema de validaciones a mano. La parte difícil es hacerlo tan genérico posible para poder usarlo en diferentes navegadores y en los diferentes formularios que crees. Hay muchas bibliotecas disponibles para realizar validaciones de formularios; no descartes el usarlas. Aquí hay algunos ejemplos:

Validación remota

En algunos casos, puede ser útil realizar validaciones remotas. Esta clase de validación es necesaria cuando los datos introducidos por el usuario están ligados a datos adicionales almacenados en el lado del servidor de tu aplicación. Un caso de uso para esto es el formulario de registro, donde se te pide un nombre de usuario. Para evitar duplicados, es mejor realizar una solicitud AJAX para verificar la disponibilidad del nombre de usuario, en lugar de mostrar el error hasta el momento que el usuario envíe los datos y el servidor devuelva la respuesta.

Realizar una validación como ésa requiere tomar ciertas precauciones:

  • Se necesita exponer una API y datos públicamente; y asegurarse que no sean datos sensibles.
  • Por casos de retraso en la red, es necesario que se realicen validaciones asíncronas. Esto requiere algo de trabajo en UI para asegurar que el usuario no será bloqueado si la validación no se realiza correctamente.

Conclusión

La validación de formularios no necesita JavaScript complejo, pero sí requiere pensar cuidadosamente en los usuarios. Siempre recuerda ayudar a tu usuario a corregir los datos que proporciona. Para esto, asegúrate de:

  • Mostrar mensajes de error explícitos.
  • Ser permisivo en cuanto al formato de captura.
  • Señalar exactamente dónde ocurre el error (especialmente en formularios largos).

Etiquetas y colaboradores del documento

 Colaboradores en esta página: israel-munoz
 Última actualización por: israel-munoz,