Creación de controles de formularios personalizados con ElementInternals

Desde el principio de los tiempos, la humanidad ha soñado con tener más control sobre los elementos de la forma. Vale, puede que esté exagerando un poco, pero crear o personalizar componentes de formulario ha sido el santo grial del desarrollo web front-end durante años.
Una de las características menos anunciadas, pero más poderosas, de los elementos personalizados (p. ej. my-custom-element
) se ha abierto camino silenciosamente en Google Chrome a partir de la versión 77 y se está abriendo camino en otros navegadores. El ElementInternals
estándar es un conjunto de características muy interesantes con un nombre muy sencillo. Entre las características internas agregadas se encuentran la capacidad de participar en formularios y una API en torno a los controles de accesibilidad.
En este artículo, veremos cómo crear un control de formulario personalizado, integrar la validación de restricciones, presentar los conceptos básicos de accesibilidad interna y ver una manera de combinar estas características para crear un control de formulario macro altamente portátil.
Comenzamos a crear un elemento personalizado muy simple que coincide con nuestro sistema de diseño. Nuestro elemento mantendrá todos sus estilos dentro del DOM oculto y garantizará cierta accesibilidad básica. Usaremos la maravillosa biblioteca del equipo Polymer de Google para nuestros ejemplos de código y, aunque definitivamente no la necesitas, proporciona una gran abstracción para escribir elementos personalizados.LitElement
En este Pen, hemos creado uno rad-input
que tiene un diseño básico. También agregamos una segunda entrada a nuestro formulario que es una entrada HTML básica y agregamos un valor predeterminado (para que pueda simplemente presionar enviar y verlo funcionar).
Cuando hacemos clic en nuestro botón de enviar, suceden algunas cosas. Primero, se llama al método del evento de envío preventDefault
, en este caso, para garantizar que nuestra página no se vuelva a cargar. Después de esto, creamos un FormData
objeto que nos da acceso a información sobre nuestro formulario que usamos para construir una cadena JSON y agregarla a un output
elemento. Tenga en cuenta, sin embargo, que el único valor agregado a nuestra salida proviene del elemento con name="basic"
.
Esto se debe a que nuestro elemento aún no sabe cómo interactuar con el formulario, así que configuramos nuestro rad-input
con una ElementInternals
instancia para ayudarlo a hacer honor a su nombre. Para comenzar, necesitaremos llamar al attachInternals
método de nuestro método en el constructor del elemento, también importaremos un ElementInternals
polyfill a nuestra página para trabajar con navegadores que aún no admitan la especificar.
El attachInternals
método devuelve una nueva instancia interna de elemento que contiene algunas API nuevas que podemos usar en nuestro método. Para permitir que nuestro elemento aproveche estas API, debemos agregar un formAssociated
captador estático que devuelva true
.
class RadInput extends LitElement { static get formAssociated() { return true; } constructor() { super(); this.internals = this.attachInternals(); }}
Echemos un vistazo a algunas de las API en internals
la propiedad de nuestro elemento:
setFormValue(value: string|FormData|File, state?: any): void
— Este método establecerá el valor del elemento en su formulario principal, si hay uno presente. Si el valor esnull
, el elemento no participará en el proceso de envío del formulario.form
— Una referencia a la forma principal de nuestro elemento, si existe.setValidity(flags: PartialValidityState, message?: string, anchor?: HTMLElement): void
— ElsetValidity
método ayudará a controlar el estado de validez de nuestro elemento dentro del formulario. Si el formulario no es válido, debe estar presente un mensaje de validación.willValidate
— Serátrue
si el elemento será evaluado cuando se envíe el formulario.validity
— Un objeto de validez que coincide con las API y la semántica adjuntas aHTMLInputElement.prototype.validity
.validationMessage
— Si el control se introduce como no válido consetValidity
, este es el mensaje que se pasó al describir el error.checkValidity
— Regresarátrue
si el elemento es válido; De lo contrario, regresaráfalse
y activará uninvalid
evento en el elemento.reportValidity
— Hace lo mismo quecheckValidity
e informará de los problemas al usuario si el evento no se cancela.labels
— Una lista de elementos que etiquetan este elemento usando ellabel[for]
atributo.- Varios otros controles se utilizan para establecer información sobre el elemento.
Establecer el valor de un elemento personalizado
Modifiquemos nuestro rad-input
para aprovechar algunas de estas API:
Aquí hemos modificado el _onInput
método del elemento para incluir una llamada a this.internals.setFormValue
. Esto le dice al formulario que nuestro elemento quiere registrar un valor con el formulario bajo su nombre de pila (que se establece como un atributo en nuestro HTML). También agregamos un firstUpdated
método (más o menos análogo a connectedCallback
cuando no se usa LitElement
) que establece el valor del elemento en una cadena vacía cada vez que el elemento termina de renderizarse. Esto es para asegurar que nuestro elemento siempre tenga un valor con el formulario (y aunque no es necesario, es posible que desees eliminar tu elemento del formulario pasando un null
valor).
Ahora, cuando agreguemos un valor a nuestra entrada y enviemos el formulario, veremos que tenemos un radInput
valor en nuestro output
elemento. También podemos ver que nuestro elemento se ha agregado a la HTMLFormElement
propiedad radInput
. Sin embargo, una cosa que quizás hayas notado es que a pesar de que nuestro elemento no tiene un valor, aún permitirá que se realice el envío del formulario. A continuación, agregamos algo de validación a nuestro elemento.
Agregar validación de restricciones
Para configurar la validación de nuestro campo, necesitamos modificar un poco nuestro elemento para hacer uso del setValidity
método en el objeto interno de nuestro elemento. Este método aceptará tres argumentos (el segundo sólo es necesario si el elemento no es válido, el tercero siempre es opcional). El primer argumento es un ValidityState
objeto parcial. Si se establece algún indicador en true
el control, se marcará como no válido. Si una de las claves de validez integradas no satisface sus necesidades, existe una customError
clave general que debería funcionar. Por último, si el control es válido, pasamos un objeto literal ( {}
) para restablecer la validez del control.
El segundo argumento aquí es el mensaje de validez del control. Este argumento es obligatorio si el control no es válido y no se permite si el control es válido. El tercer argumento es un objetivo de validación opcional que controlará el enfoque del usuario si el formulario se envía como no válido o reportValidity
se llama.
Vamos a introducir un nuevo método rad-input
que se encargará de esta lógica por nosotros:
_manageRequired() { const { value } = this; const input = this.shadowRoot.querySelector('input'); if (value === '' this.required) { this.internals.setValidity({ valueMissing: true }, 'This field is required', input); } else { this.internals.setValidity({}); }}
Esta función obtiene el valor y la entrada del control. Si el valor es igual a una cadena vacía y el elemento está marcado como requerido, llamaremos internals.setValidity
y alternaremos la validez del control. Ahora todo lo que tenemos que hacer es llamar a este método en nuestros métodos firstUpdated
y _onInput
y habremos agregado algo de validación básica a nuestro elemento.
Al hacer clic en el botón Enviar antes de ingresar un valor en nuestro rad-input
ahora se mostrará un mensaje de error en los navegadores que admiten la ElementInternals
especificar. Desafortunadamente, Polyfill aún no admite la visualización de errores de validación, ya que no existe ninguna forma confiable de activar la ventana emergente de validación incorporada en navegadores que no lo admiten.
También agregamos información básica de accesibilidad a nuestro ejemplo usando nuestro internals
objeto. Hemos agregado una propiedad adicional a nuestro elemento, _required
que servirá como proxy this.required
y como captador/definidor para required
.
get required() { return this._required;}set required(isRequired) { this._required = isRequired; this.internals.ariaRequired = isRequired;}
Al pasar la required
propiedad a internals.ariaRequired
, alertamos a los lectores de pantalla de que nuestro elemento actualmente espera un valor. En el polyfill, esto se hace agregando un aria-required
atributo; Sin embargo, en los navegadores compatibles, el atributo no se agregará al elemento porque esa propiedad es inherente al elemento.
Creando una microforma
Ahora que tenemos una entrada de trabajo que se adapta a nuestro sistema de diseño, es posible que deseemos comenzar a componer nuestros elementos en patrones que podamos reutilizar en varias aplicaciones. Una de las características más atractivas ElementInternals
es que el setFormValue
método puede tomar no solo datos de cadenas y archivos, sino también FormData
objetos. Entonces, digamos que queremos crear un formulario de dirección común que pueda usar en múltiples organizaciones, podemos hacerlo fácilmente con nuestros elementos recién creados.
En este ejemplo, tenemos un formulario creado dentro de la raíz de sombra de nuestro elemento donde hemos compuesto cuatro rad-input
elementos para crear un formulario de dirección. En lugar de llamar setFormValue
con una cadena, esta vez hemos elegido pasar el valor completo de nuestro formulario. Como resultado, nuestro elemento pasa los valores de cada elemento individual dentro de su forma secundaria a la forma exterior.
Agregar validación de restricciones a este formulario sería un proceso bastante sencillo, al igual que proporcionar estilos, comportamientos y ubicaciones adicionales en el contenido. El uso de estas API más nuevas finalmente permite a los desarrolladores desbloquear una tonelada de potencial dentro de elementos personalizados y finalmente nos brinda libertad para controlar nuestras experiencias de usuario.
Deja un comentario