Publicado: 8 de agosto de 2019
Muchos desarrolladores crean controles de formulario personalizados, ya sea para proporcionar controles que no están integrados en el navegador o para personalizar el aspecto más allá de lo que es posible con los controles de formulario integrados.
Sin embargo, puede ser difícil replicar las funciones de los controles de formulario HTML integrados. Ten en cuenta algunas de las funciones que un elemento <input> obtiene automáticamente cuando lo agregas a un formulario:
- La entrada se agrega automáticamente a la lista de controles del formulario.
- El valor de la entrada se envía automáticamente con el formulario.
- La entrada participa en la validación de formularios . Puedes dar estilo a la entrada usando las pseudoclases
:validy:invalid. - El usuario recibe una notificación cuando se restablece el formulario, cuando se recarga o cuando el navegador intenta autocompletar los campos del formulario.
Por lo general, los controles de formulario personalizados tienen pocas de estas funciones. Los desarrolladores pueden sortear algunas de las limitaciones de JavaScript, como agregar un <input> oculto a un formulario para participar en el envío del formulario. Sin embargo, otras funciones no se pueden replicar solo en JavaScript.
Dos funciones web facilitan la creación de controles de formulario personalizados y eliminan las limitaciones de los controles personalizados:
- El evento
formdatapermite que un objeto JavaScript arbitrario participe en el envío de formularios, por lo que puedes agregar datos de formulario sin usar un<input>oculto. - La API de elementos personalizados asociados a formularios permite que los elementos personalizados actúen más como controles de formularios integrados.
Estas dos funciones se pueden usar para crear nuevos tipos de controles que funcionen mejor.
API basada en eventos
El evento formdata es una API de bajo nivel que permite que cualquier código JavaScript participe en el envío de formularios.
- Agrega un objeto de escucha de eventos
formdataal formulario con el que deseas interactuar. - Cuando un usuario hace clic en enviar, el formulario activa un evento
formdata, que incluye un objetoFormDataque contiene todos los datos que se envían. - Cada objeto de escucha
formdatatiene la oportunidad de agregar o modificar los datos antes de que se envíe el formulario.
Este es un ejemplo de cómo enviar un solo valor en un objeto de escucha de eventos formdata:
const form = document.querySelector('form');
// FormData event is sent on <form> submission, before transmission.
// The event has a formData property
form.addEventListener('formdata', ({formData}) => {
// https://developer.mozilla.org/docs/Web/API/FormData
formData.append('my-input', myInputValue);
});
Elementos personalizados asociados a formularios
Puedes usar la API basada en eventos con cualquier tipo de componente, pero solo te permite interactuar con el proceso de envío.
Los controles de formulario estandarizados participan en muchas partes del ciclo de vida del formulario. Los elementos personalizados asociados a formularios tienen como objetivo cerrar la brecha entre los widgets personalizados y los controles integrados. Los elementos personalizados asociados a formularios coinciden con muchas de las características de los elementos de formulario estandarizados:
- Cuando colocas un elemento personalizado asociado a un formulario dentro de un
<form>, se asocia automáticamente con el formulario, como un control proporcionado por el navegador. - El elemento se puede etiquetar con un elemento
<label>. - El elemento puede establecer un valor que se envía automáticamente con el formulario.
- El elemento puede establecer una marca que indique si tiene o no una entrada válida. Si uno de los controles del formulario tiene una entrada no válida, no se puede enviar el formulario.
- El elemento puede proporcionar devoluciones de llamada para varias partes del ciclo de vida del formulario, como cuando el formulario se inhabilita o se restablece a su estado predeterminado.
- El elemento admite las seudoclases de CSS estándar para los controles de formulario, como
:disabledy:invalid.
En este documento, no se abarca todo, pero se describen los conceptos básicos necesarios para integrar tu elemento personalizado en un formulario.
Cómo definir un elemento personalizado asociado a un formulario
Para convertir un elemento personalizado en un elemento personalizado asociado a un formulario, se requieren algunos pasos adicionales:
- Agrega una propiedad
formAssociatedestática a la clase de elemento personalizado. Esto le indica al navegador que trate el elemento como un control de formulario. - Llama al método
attachInternals()en el elemento para acceder a métodos y propiedades adicionales para los controles de formulario, comosetFormValue()ysetValidity(). - Agrega las propiedades y los métodos comunes admitidos por los controles de formulario, como
name,valueyvalidity.
A continuación, se muestra cómo se ajustan esos elementos en una definición básica de elemento personalizado:
// Form-associated custom elements must be autonomous custom elements.
// They must extend HTMLElement, not one of its subclasses.
class MyCounter extends HTMLElement {
// Identify the element as a form-associated custom element
static formAssociated = true;
constructor() {
super();
// Get access to the internal form control APIs
this.internals_ = this.attachInternals();
// internal value for this control
this.value_ = 0;
}
// Form controls usually expose a "value" property
get value() { return this.value_; }
set value(v) { this.value_ = v; }
// The following properties and methods aren't strictly required,
// but browser-level form controls provide them. Providing them helps
// ensure consistency with browser-provided controls.
get form() { return this.internals_.form; }
get name() { return this.getAttribute('name'); }
get type() { return this.localName; }
get validity() {return this.internals_.validity; }
get validationMessage() {return this.internals_.validationMessage; }
get willValidate() {return this.internals_.willValidate; }
checkValidity() { return this.internals_.checkValidity(); }
reportValidity() {return this.internals_.reportValidity(); }
…
}
customElements.define('my-counter', MyCounter);
Una vez que se registra, puedes usar este elemento donde usarías un control de formulario proporcionado por el navegador:
<form>
<label>Number of bunnies: <my-counter></my-counter></label>
<button type="submit">Submit</button>
</form>
Establecer un valor
El método attachInternals() devuelve un objeto ElementInternals que proporciona acceso a las APIs de control de formularios. El más básico de estos es el método setFormValue(), que establece el valor actual del control.
El método setFormValue() puede tomar uno de los siguientes tres tipos de valores:
- Es un valor de cadena.
- Un objeto
File. - Un objeto
FormData. Puedes usar un objetoFormDatapara pasar varios valores. Por ejemplo, un control de entrada de tarjeta de crédito podría pasar un número de tarjeta, una fecha de vencimiento y un código de verificación.
Para establecer un valor, haz lo siguiente:
this.internals_.setFormValue(this.value_);
Para establecer varios valores, puedes hacer lo siguiente:
// Use the control's name as the base name for submitted data
const n = this.getAttribute('name');
const entries = new FormData();
entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);
Validación de entradas
Tu control también puede participar en la validación de formularios llamando al método setValidity() en el objeto internals.
// Assume this is called whenever the internal value is updated
onUpdateValue() {
if (!this.matches(':disabled') && this.hasAttribute('required') &&
this.value_ < 0) {
this.internals_.setValidity({customError: true}, 'Value cannot be negative.');
}
else {
this.internals_.setValidity({});
}
this.internals.setFormValue(this.value_);
}
Puedes aplicar un diseño a un elemento personalizado asociado a un formulario con las pseudoclases :valid y :invalid, al igual que con un control de formulario integrado.
Devoluciones de llamada del ciclo de vida
Una API de elementos personalizados asociados a formularios incluye un conjunto de devoluciones de llamada de ciclo de vida adicionales para vincularse al ciclo de vida del formulario. Las devoluciones de llamada son opcionales: solo implementa una devolución de llamada si tu elemento necesita hacer algo en ese punto del ciclo de vida.
void formAssociatedCallback(form)
Se llama cuando el navegador asocia el elemento con un elemento de formulario o cuando lo desasocia de un elemento de formulario.
void formDisabledCallback(disabled)
Se llama después de que cambia el estado disabled del elemento, ya sea porque se agregó o quitó el atributo disabled de este elemento, o bien porque cambió el estado disabled en un <fieldset> que es un elemento superior de este elemento.
Por ejemplo, el elemento puede inhabilitar elementos en su DOM de sombra cuando está inhabilitado.
void formResetCallback()
Se llama después de que se restablece el formulario. El elemento debe restablecerse a algún tipo de estado predeterminado. Para los elementos <input>, esto generalmente implica establecer la propiedad value para que coincida con el atributo value establecido en el marcado. Con una casilla de verificación, esto se relaciona con establecer la propiedad checked para que coincida con el atributo checked.
void formStateRestoreCallback(state, mode)
Se llama en una de las siguientes dos circunstancias:
- Cuando el navegador restablece el estado del elemento, por ejemplo, después de la navegación o cuando se reinicia el navegador. El argumento
modees"restore". - Cuando las funciones de asistencia de entrada del navegador, como el autocompletado de formularios, establecen un valor. El argumento
modees"autocomplete".
El tipo del primer argumento depende de cómo se llamó al método setFormValue().
Restablece el estado del formulario
En algunas circunstancias, como cuando se vuelve a una página o se reinicia el navegador, es posible que el navegador intente restablecer el formulario al estado en el que lo dejó el usuario.
En el caso de un elemento personalizado asociado a un formulario, el estado restablecido proviene de los valores que pasas al método setFormValue(). Puedes llamar al método con un solo parámetro de valor, como se muestra en los ejemplos anteriores, o con dos parámetros:
this.internals_.setFormValue(value, state);
El value representa el valor que se puede enviar del control. El parámetro opcional state es una representación interna del estado del control, que puede incluir datos que no se envían al servidor. El parámetro state toma los mismos tipos que el parámetro value: una cadena, File o un objeto FormData.
El parámetro state es útil cuando no puedes restablecer el estado de un control solo en función del valor. Por ejemplo, supongamos que creas un selector de color con varios modos: una paleta o una rueda de color RGB. El valor que se puede enviar es el color seleccionado en un formato canónico, como "#7fff00". Para restablecer el control a un estado específico, también deberás saber en qué modo estaba, por lo que el estado podría verse como "palette/#7fff00".
this.internals_.setFormValue(this.value_,
this.mode_ + '/' + this.value_);
Tu código debería restablecer su estado en función del valor de estado almacenado.
formStateRestoreCallback(state, mode) {
if (mode == 'restore') {
// expects a state parameter in the form 'controlMode/value'
[controlMode, value] = state.split('/');
this.mode_ = controlMode;
this.value_ = value;
}
// Chrome doesn't handle autofill for form-associated custom elements.
// In the autofill case, you might need to handle a raw value.
}
En el caso de un control más simple (por ejemplo, una entrada de número), es probable que el valor sea suficiente para restablecer el control a su estado anterior. Si omites state cuando llamas a setFormValue(), el valor se pasa a formStateRestoreCallback().
formStateRestoreCallback(state, mode) {
// Simple case, restore the saved value
this.value_ = state;
}
Detección de características
Puedes usar la detección de funciones para determinar si el evento formdata y los elementos personalizados asociados a formularios están disponibles. No se lanzó ningún polyfill para ninguna de las dos funciones. En ambos casos, puedes recurrir a agregar un elemento de formulario oculto para propagar el valor del control al formulario.
Es probable que muchas de las funciones más avanzadas de los elementos personalizados asociados a formularios sean difíciles o imposibles de implementar con polyfill.
if ('FormDataEvent' in window) {
// formdata event is supported
}
if ('ElementInternals' in window &&
'setFormValue' in window.ElementInternals.prototype) {
// Form-associated custom elements are supported
}
El evento formdata te proporciona una interfaz para agregar los datos de tu formulario al proceso de envío, sin tener que crear un elemento <input> oculto. Con la API de elementos personalizados asociados a formularios, puedes proporcionar un nuevo conjunto de capacidades para los controles de formularios personalizados que funcionan como los controles de formularios integrados.