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 ElementInternalsestá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-inputque 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 FormDataobjeto que nos da acceso a información sobre nuestro formulario que usamos para construir una cadena JSON y agregarla a un outputelemento. 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-inputcon una ElementInternalsinstancia para ayudarlo a hacer honor a su nombre. Para comenzar, necesitaremos llamar al attachInternalsmétodo de nuestro método en el constructor del elemento, también importaremos un ElementInternalspolyfill a nuestra página para trabajar con navegadores que aún no admitan la especificar.

El attachInternalsmé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 formAssociatedcaptador 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 internalsla 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 es null, 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 — El setValiditymé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á truesi 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 a HTMLInputElement.prototype.validity.
  • validationMessage — Si el control se introduce como no válido con setValidity, este es el mensaje que se pasó al describir el error.
  • checkValidity — Regresará truesi el elemento es válido; De lo contrario, regresará falsey activará un invalidevento en el elemento.
  • reportValidity — Hace lo mismo que checkValiditye informará de los problemas al usuario si el evento no se cancela.
  • labels — Una lista de elementos que etiquetan este elemento usando el label[for]atributo.
  • Varios otros controles se utilizan para establecer información sobre el elemento.

Establecer el valor de un elemento personalizado

Modifiquemos nuestro rad-inputpara aprovechar algunas de estas API:

Aquí hemos modificado el _onInputmé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 firstUpdatedmétodo (más o menos análogo a connectedCallbackcuando 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 nullvalor).

Ahora, cuando agreguemos un valor a nuestra entrada y enviemos el formulario, veremos que tenemos un radInputvalor en nuestro outputelemento. También podemos ver que nuestro elemento se ha agregado a la HTMLFormElementpropiedad 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 setValiditymé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 ValidityStateobjeto parcial. Si se establece algún indicador en trueel control, se marcará como no válido. Si una de las claves de validez integradas no satisface sus necesidades, existe una customErrorclave 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 reportValidityse llama.

Vamos a introducir un nuevo método rad-inputque 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.setValidityy alternaremos la validez del control. Ahora todo lo que tenemos que hacer es llamar a este método en nuestros métodos firstUpdatedy _onInputy 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-inputahora se mostrará un mensaje de error en los navegadores que admiten la ElementInternalsespecificar. 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 internalsobjeto. Hemos agregado una propiedad adicional a nuestro elemento, _requiredque servirá como proxy this.requiredy como captador/definidor para required.

get required() {  return this._required;}set required(isRequired) {  this._required = isRequired;  this.internals.ariaRequired = isRequired;}

Al pasar la requiredpropiedad 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-requiredatributo; 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 ElementInternalses que el setFormValuemétodo puede tomar no solo datos de cadenas y archivos, sino también FormDataobjetos. 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-inputelementos para crear un formulario de dirección. En lugar de llamar setFormValuecon 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

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir