¿Cómo crear un componente web?  Shadow DOM y etiqueta template

¿Cómo crear un componente web? Shadow DOM y etiqueta template

¿Cómo logramos encapsulación de nuestros nuevos elementos HTML que no provoque conflictos con el resto de una aplicación?, es decir, que los estilos, HTML interno y JS de un componente web, no colisionen con el resto.

La respuesta básicamente es, el Shadow DOM. El Shadow DOM nos permite tener los estilos y contenido HTML interno totalmente aislado del exterior. También se ayuda del API del DOM and custom elements para establecer comunicación con otros elementos y componentes internos. Además con el exterior.

El amor es de naturaleza interna. No lo vas a encontrar en alguien más, naces con ello, es un componente interno de cada persona. Mira a un bebé, simplemente es feliz por el hecho de existir. Irradia amor, te lo comparte.

Anónimo

Antes de comenzar, si aún no estás familiarizado con los custom elements y los componentes web nativos en general, te recomiendo revisar primero estas publicaciones:

  1. Componentes web, ¿Qué son? POO
  2. ¿Cómo crear un componente web? Elementos HTML personalizados

Primero vamos a revisar la etiqueta <template>, para luego utilizarla dentro de nuestro componente web usando shadow DOM.

¿Qué es una plantilla?

Un template o plantilla HTML con el tag <template>, es una forma de crear elementos visuales de manera sencilla y eficiente, de tal forma que se pueden definir partes de una página web o aplicación a las cuales les podemos insertar valores.

Este template puede ser reutilizado en diferentes vistas de una aplicación. Tal vez estés familiarizado con jade, pug, mustache, o con algunos frameworks y librerías como angular y vue, los cuales utilizan templates. También si has utilizado wordpress y php, los archivos de los temas serian una especie de templates.

Ahora, los templates que vamos a aprender son específicos de HTML y por lo tanto funcionan en los navegadores web. Para entrar un poco más en contexto veamos un ejemplo sencillo de como podemos crear varios elementos para mostrar un saludo, utilizaremos estas tres fuciones, que forman parte del API del DOM.

  • document.createDocumentFragment()
  • document.createElement('nombreTag', [opciones])
  • nodo.appendChild(nodo)

Queremos mostrar en nuestra aplicación un saludo al usuario y lo hacemos de la siguiente forma:

Ahora veamos como hacer lo mismo, pero utilizando la etiqueta <template>:

Como podemos ver, con la etiqueta <template> se usa mucho menos código, si el contenido de lo que queremos mostrar crece, solo debemos agregar las etiquetas y demás contenido de manera sencilla dentro de la etiqueta <template>. Pero si usamos el primer método, nuestro código javascript crecerá mucho más, esto es más difícil de entender y mantener con el tiempo.

La etiqueta template usa internamente un DocumentFragment, la ventaja de utilizar document fragments es que su contenido no es aún agregado al árbol de nodos, sino que se encuentra en memoria y se inserta una sola vez al final, cuando el contenido esta completo.

// Agregar document fragment a la página
host.appendChild(df);

Existen 4 puntos importantes al utilizar la etiqueta <template> para crear plantillas:

  • The label <template> no se pinta en nuestra aplicación, por default tiene un display: none;.
  • El contenido de la etiqueta <template> es un document fragment, el cual tiene la característica de no estar insertado en el árbol de nodos. Esto es bueno para el rendimiento de la aplicación debido al alto costo de pintar elementos.
  • El código JS necesario es muy simple y corto
  • Cuando clonamos el contenido de nuestro template usando cloneNode(), debemos pasar el valor true como parámetro, de otra manera NO clonaríamos los elementos hijos del document fragment de nuestro template.

Shadow DOM

El shadow DOM es un DOM o árbol de nodos en las sombras, escondidos de los demás elementos de una aplicación. Para entender esto vamos a crear nuestro primer componente web utilizando el estándar de custom elements, la etiqueta <template> y el shadow DOM.

Nuestro componente se llama mi-saludo, con el contenido del ejemplo anterior:

Si aún no sabes que son los custom elements, sigue este link. En el ejemplo anterior creamos un custom element llamado mi-saludo, dentro del constructor() creamos una instancia del template.

// Obtengo la única etiqueta 'template'
const tpl = document.querySelector('template');
// Clono su contenido y se crea una instancia del document fragment
const tplInst = tpl.content.cloneNode(true);

Luego creamos un shadow DOM y lo adjuntamos al custom element mi-saludo:

// Se crea un nuevi shadow DOM para las instancias de mi-saludo
this.attachShadow({ mode: 'open'});

Y finalmente agregamos el contenido clonado del template dentro del shadow DOM:

// Y se agrega el template dentro del shadow DOM usando el elemento raíz 'shadowRoot'
this.shadowRoot.appendChild(tplInst);

En este último paso usamos la propiedad shadowRoot, creada cundo invocamos attachShadow, esta propiedad hace referencia a un nodo raíz especial de donde se empieza a colgar todo lo que definimos dentro de nuestra etiqueta <template>, y es a partir de este nodo que agregamos contenido.

Lo que se agrega al shadow DOM esta oculto para el exterior, ningún document.querySelector() externo puede acceder a los elementos del shadow DOM y los estilos definidos en el exterior no pueden acceder a estos elementos ocultos. Esto quiere decir que dentro del shadow DOM podemos utilizar atributos id and name sin temor a colisionar con los ids y name del exterior.

Cuando creamos un shadow DOM, se hace con la propiedad mode: open, de esta manera se puede acceder al contenido del shadow DOM usando Javascript y el atributo shadowRoot. Sin el atributo shadowRoot es imposible acceder a los elementos y eso nos proporciona encapsulación a nuestro componente.

Para crear una nueva instancia de nuestro nuevo componente, solo utilizamos el nombre que registramos como etiqueta:

// Se registra el custom element para poder ser utilizado declarativamente en el HTML o imperativamente mediante JS
customElements.define('mi-saludo', MiSaludo);

Y en el html utilizamos código declarativo:

<mi-saludo></mi-saludo>

¿Qué pasa si a nuestro template le agregamos una etiqueta style? Debido a que las instancias de nuestro template son agregadas al shadow DOM, entonces esos estilos solo afectan dentro del shadow DOM.

Para establecer estilos dentro del shadow DOM al nuevo tag creado, se utiliza la pseudoclase :host, que hace referencia a la etiqueta huésped del shadow DOM, es decir, mi-saludo.

También agregamos en el exterior estilos para las etiquetas <h1>, estos estilos no pudieron afectar al contenido interno del shadow DOM, el único h1#mi-id con borde rojo es el que se encuentra fuera de nuestro componente. El h1 externo como el interno tienen el mismo id, a causa del encapsulamiento con shadow DOM, no colisionan los estilos con el mismo selector de id.

Ahora en nuestro código html, agreguemos más etiquetas mi-saludo:

Como podemos ver la combinación de estas tres tecnologías; custom elements, template y shadow DOM nos permite crear nuevas etiquetas personalizadas con su propio contenido interno sin afectar el exterior y también el exterior no puede afectar directamente la implementación interna de nuestro componente. En el último ejemplo podemos ver como creamos cuatro instancias de nuestro componente simplemente utilizando etiquetas, es decir, reutilizando nuestro componente encapsulado.

En la próxima publicación veremos como implementar más funcionalidad Javascript encapsulada del componente web y también más características muy útiles del shadow DOM como composición, eventos y más estilos. Finalmente veremos los módulos de JS para completar un componente web reutilizable que pueda ser importado en cualquier otra aplicación web.

¿Qué elementos de HTML pueden tener Shadow DOM?

¿Qué elementos de HTML pueden tener Shadow DOM?

Cuando trabajamos con componentes web, normalmente usamos el shadow DOM en los custom elements que creamos debido a que es la manera en que podemos sacarle todo el potencial a este estándar. Pero seguro que más de una ocasión nos hemos preguntado, ¿Qué elementos de HTML NO pueden aceptar Shadow DOM? Bueno, existen dos formas de saberlo.

  • La especificación del DOM Standard
  • Herramientas de desarrollador de Google Chrome

La especificación del DOM Standard

La manera más eficiente de saber exactamente que elementos NO soportan shadow DOM es guiarnos por lo que dice la especificación.

https://dom.spec.whatwg.org/#dom-element-attachshadow

En el punto 2 dice:

If this’s local name is not one of the following:

  • to valid custom element name
  • article“, “aside“, “blockquote“, “body“, “div“, “footer“, “h1“, “h2“, “h3“, “h4“, “h5“, “h6“, “header“, “main“, “nav“, “p“, “section“, or “span

then throw a “NotSupportedError” DOMException.

Lo anterior se traduce de la siguiente manera.

Si el nombre del elemento es:

  • Un nombre válido de custom element, que básicamente es un nombre con un guion medio.
  • article“, “aside“, “blockquote“, “body“, “div“, “footer“, “h1“, “h2“, “h3“, “h4“, “h5“, “h6“, “header“, “main“, “nav“, “p“, “section“, or “span

Entonces si le puedes agregar Shadow DOM

Herramienta de desarrollo de Google Chrome

La otra opción es usar las herramientas del desarrollador de Google Chrome para ver el shadow DOM de los elementos en una aplicación.

Esta configuración se encuentra en los settings del panel de herramientas para el desarrollador, para ello, debemos dar clic en los tres puntitos verticales y después en settings o solamente presionar F1.

Luego en Preferences  -> Elements activamos el checkbox que dice Show user agent shadow DOM.

Ahora podemos revisar sitios web que tengan elementos que soporten shadow DOM, algunos ejemplos son el input, select, audio, video, estos elementos ya contienen shadow DOM y por lo tanto NO podemos adjuntarles uno. En la siguiente imagen se muestra el elemento input :

También existen otros elementos que NO soportan Shadow DOM, ejemplo,
img, a, button, br, b, etc.

Tanto los elementos que ya tiene Shadow DOM como los que no lo soportan provocaran el siguiente error al invocar sobre ellos el método attachShadow({ mode: 'open' }). Tal como dice la especificación

Uncaught DOMException: Failed to execute ‘attachShadow’ on ‘Element’: This element does not support attachShadow

Consola de Google Chrome

NotSupportedError: Operation is not supported

Consola de Firefox

Object doesn’t support property or method ‘attachShadow’

Consola Microsoft Edge
¿Cómo crear un componente web? Elementos HTML personalizados

¿Cómo crear un componente web? Elementos HTML personalizados

En este artículo vamos a utilizar el API de Elementos HTML personalizados) para crear nuevas etiquetas y utilizarlas en nuestro código HTML, en futuras publicaciones explicaremos como se usan en conjunto con los otros 3 estándares. Para crear un componente web se utilizan 4 tecnologías principales estándares de la web.

  • Custom elements o elementos HTML personalizados.
  • Shadow DOM, DOM en las sombras.
  • Carga de módulos o componentes externos.
    • HTML imports con la etiqueta <link rel="import">.
    • <script type="module">, cargando módulos usando el tag script. Esta tecnología sustituye a los html imports.

Para más información sobre componentes web te recomiendo que leas este artículo Cómo crear mejores aplicaciones con los fascinantes componentes web

Cada uno de estos ciclos es un evento único; dieta, elección, selección, estación, clima, digestión, descomposición y regeneración, difieren cada vez que ocurren. Por lo tanto, es el número de estos ciclos, grandes y pequeños, lo que decide el potencial de diversidad. Deberíamos sentirnos privilegiados de ser parte de esa renovación eterna. Con solo vivir hemos logrado la inmortalidad como hierba, saltamontes, gaviotas, gansos y otras personas. Somos parte de la diversidad que experimentamos en todos los sentidos.
Si, como nos aseguran los científicos, todos tenemos moléculas de Einstein, y si las partículas atómicas de nuestro cuerpo físico llegan a los límites más externos del universo, entonces todos somos en efecto componentes de todas las cosas. No nos queda ningún lugar adonde ir si ya estamos en todas partes, y esto es, en verdad, todo lo que tendremos o necesitaremos. Si nos amamos a nosotros mismos, deberíamos respetar todas las cosas por igual y no reclamar ninguna superioridad sobre lo que son, en efecto, nuestras otras partes. ¿Es la mano superior al ojo? ¿El hijo de la madre?

Bill Mollison

Elementos HTML Personalizados

El estándar Custom elements nos indica dos maneras de extender la funcionalidad del HTML:

  • Crear nuestros propios elementos desde cero, nuevos elementos a los que le agregamos toda la funcionalidad que sea necesaria.
  • Extender la funcionalidad de elementos ya existentes. Lamentablemente esta característica no está soportada por todos los navegadores modernos. En la versión v0 si había soporte, pero esa versión ya está obsoleta.
    • Desde la versión 67 de chrome esto funciona correctamente, Firefox lo tiene en desarrollo, Edge lo va a implementar, lamentablemente safari decidió no implementarlo.
    • Más información en https://www.chromestatus.com/feature/4670146924773376

Para entender la diferencia, en el primer caso podríamos crear un elemento llamado <mi-boton>, al que tendríamos que agregar estilos y funcionalidad desde cero para que se comporte como un botón.

En el segundo, podemos crear un elemento <mi-boton-extendido>, el cual extiende de la etiqueta <button>, entonces este componente si tendría los estilos, eventos y demás comportamientos de un botón, es decir, hereda el comportamiento de <button> y entonces solo se le agregaría algún comportamiento extra para cumplir con el objetivo en mente.

Si en los ejemplos no lanza el alerta especificado en el código, siempre puedes presionar “edit on codepen” para que el alerta funcione y puedes ver el código en un espacio más grande.

Crear un nuevo elemento desde cero

Veamos la primera forma para crear un nuevo tipo de elemento, desde cero.

Hay algunas cosas importantes que remarcar en el código:

  • Todos los elementos deben tener un prefijo, esto es un requisito para evitar que en futuras versiones de HTML las nuevas etiquetas colisionen con los elementos creados por ti u otros programadores.
  • Podemos registrar un elemento en dos pasos, creando la clase y pasando su referencia como parámetro en el método customElements.define o utilizar una clase anónima como se hizo con <mi-boton-2> directamente en el método customElements.define.
  • Una clase en Javascript no es una clase como en otros lenguajes de programación orientada a objetos, realmente es como un tipo de función especial que te permite utilizar herencia de prototipos de una forma más familiar y limpia.
  • En el constructor siempre debemos de invocar la función super(), de esta manera obtenemos la herencia, pues provoca que se invoque este mismo método en la cadena de prototipos/herencia. Si no se invoca, el navegador web te mostrara un caught ReferenceError: Must call super constructor in derived class before accesing 'this' or returning from derived constructor.
  • Para establecer estilos se usa la nuevas etiqueta creadas mi-boton and mi-boton-2. Al ser nuevas etiquetas, cualquier selector de css puede funcionar, hablamos de selector de clase, el id, pseudoselectores y pseudoelementos.

Extender la funcionalidad de elementos ya existentes

Ahora vamos a crear un nuevo elemento basado en uno ya existente, solo pondremos el ejemplo del código, realmente como mencionamos antes, no todos navegadores aún soportan este estándar, esperemos que en futuras versiones lo hagan.

Las principales diferencias cuando extendemos la funcionalidad de elementos nativos es que seguimos utilizando el nombre de la etiqueta nativa y agregamos el atributo is para indicar que será del tipo elemento que definimos. Esto en código HTML.

Pero en código Javascript es necesario en la definición (método customElements.define), utilizar un tercer parámetro donde podemos indicar de que elemento HTML vamos a extender, extends. Y definir la propiedad is a través de código JS.

Métodos del ciclo de vida de un elemento

The custom elements tiene funciones que se ejecutan en un determinado momento de su existencia, de tal manera que el autor puede ejecutar acciones, cambios o inicialización de datos en uno o mas momentos del ciclo de vida de un componente.

Si revisas la consola de la página creada por codepen, el orden en que son ejecutados estos callbacks es:

constructor: Cuando el elemento es creado
attributeChangedCallback: Cuando cambia un atributo
connectedCallback: Cuando el elemento es insertado en el documento

Para probar disconnectedCallback solo inspecciona el elemento <mi-mensaje> y elimínalo. Un mensaje de alerta parecerá informando de la eliminación.

Cuando se crea el elemento, attributeChangedCallback se ejecuta al definir por primera vez el atributo antes de que el elemento sea insertado en el DOM.

Sincronización de atributos y propiedades

Recuerda que las propiedades y los atributos son cosas diferentes, aunque los atributos se ocupen para reflejar el estado de un elemento, no son lo mismo. Un atributo se ocupa normalmente para declarar información del elemento en el código HTML y reflejar su estado, una propiedad forma parte de la instancia del elemento.

Para obtener un atributo se utiliza element.getAttribute(attributeName), para obtener una propiedad se utiliza element.property.

To define an attribute, use element.setAttribute(attributeName, value), to define a property we use element.property = value.

Attributes are stored in a property element.attributes de la instancia del elemento.

Para entender mejor esto, vamos a crear un ejemplo un poco más complejo, donde se implementa la sincronización de atributos, propiedades y el estado del elemento. Este nuevo elemento consta de dos propiedades principales, msj and casiVisible, estos se sincronizan con los atributos msj and casi-visible.

Cuando el atributo casi-visible está declarado, el elemento se opaca hasta casi  desaparecer, además el borde negro desaparece.

Cuando decimos que se sincronizan, es que un cambio en la propiedad, se refleja en el atributo y viceversa.

Puedes hacer varias pruebas, inspeccionando los elementos, agrega y elimina el atributo casi-visible.

También puedes inspeccionar el elemento y modificar el atributo msj para ver que sucede.

Las mismas pruebas las puedes hacer con las propiedades, utilizando google chrome inspecciona los elementos y en la consola ejecutas el código de abajo:

$0.msj = 'HOLA MODIFICANDO EL MENSAJE';
$0.casiVisible = false; // muestra el elemento
$0.casiVisible = true; // casi oculta el elemento

En todos los casos notarás cambios en el estado del elemento y en el contenido del mensaje.

En siguientes publicaciones veremos como utilizar el estándar template o plantilla y shadow DOM (DOM en las sombras), luego veremos el uso JS modules, para al final crear componentes completos y ver como se comunican entre ellos.

Componentes web, ¿Qué son? POO

Componentes web, ¿Qué son? POO

Para entender los componentes web, tenemos que entender un poco a la naturaleza. Como seres humanos a veces se nos olvida que somos parte de la naturaleza y que el desarrollo de software siempre trata de representar el mundo real.

Un dato curioso sobre esta perspectiva es que Alan Kay, la persona que se le atribuye la definición formal de la programación orientada a objetos tuvo como influencia la biología.

Veo el micelio como la Internet natural de la Tierra, una conciencia con la que podríamos comunicarnos. A través de la interfaz entre especies, es posible que algún día intercambiemos información con estas redes celulares sensibles. Debido a que estas redes neurológicas externalizadas sienten cualquier impresión en ellas, desde pasos hasta ramas de árboles que caen, podrían transmitir enormes cantidades de datos sobre los movimientos de todos los organismos a través del paisaje.

Paul Stamets

Los componentes web tratan de representar el mundo real

La programación y el desarrollo de software se inspira en la naturaleza. El desarrollo de software al ser una actividad humana y social, siempre se usa en busca de solucionar problemas reales de nuestro entorno.

Antes de explicar que es un componente web me gustaría mucho compartir contigo esta información sobre diseño atómico. Interesante la relación con los átomos, moléculas y organismos ¿Verdad?. La naturaleza nos enseña mucho, pues está diseñada de una manera superior y el ser humano puede aprender mucho de este gran sistema. El mundo mismo es un sistema, el universo es un sistema.

Teniendo una noción de estos componentes podemos encontrar su relación con el desarrollo de software y con ello aprender a diseñar mejor nuestros sistemas computacionales.

Communication between components

Los átomos se comunican y forman moléculas, las moléculas se comunican y forman organismos y como estos también se comunican y forman organismos más complejos. Luego esos organismos más complejos forman sistemas completos, como el sistema respiratorio o el mismo ser humano. ¡Ups!, creo que acabo de describir la esencia de la programación orientada a objetos, porque es lo mismo, desde la comunicación entre funciones, clases, componentes, módulos, hasta la comunicación entre aplicaciones.

Paso de mensajes entre células
Passing messages between cells, Image modified from “Signaling molecules and cellular receptors: Figure 1,” by OpenStax College, Biology (CC BY 3.0).

Imagina como los pulmones forman parte del sistema respiratorio, y como este último forma parte de un ser humano. El cerebro se encarga de indicarle a los pulmones cuando contraerse y cuando expandirse sin que nosotros seamos conscientes de esos movimientos. ¿Te das cuenta de la comunicación entre el cerebro y los pulmones?

También los pulmones se comunican con otro componente llamado corazón. Los pulmones limpian la sangre llena de dióxido de carbono y agregan oxígeno. Es en ese momento en que esa sangre rica en oxígeno se envía al corazón. El corazón bombea la sangre a todas las partes de nuestro cuerpo. ¿Te das cuenta de todas las comunicaciones que existen entre los órganos (componentes)?

Dicho todo el rollo anterior y después de darle por lo menos un vistazo al diseño atómico. Nos damos cuenta de la importancia de los componentes que conforman el diseño de algo más complejo. Y en nuestro caso los componentes con los que construimos nuestras aplicaciones web.

¿Qué es un componente web?

Entonces, ¿Qué es un componente web? Un componente web es lo mismo que explicamos en los párrafos anteriores solo que con el contexto de aplicaciones web. Dado que es tecnología web, se usa mucho javascript, CSS y HTML, cuando se crea un componente web. Se crea una nueva etiqueta HTML para ser reutilizada tal como usamos las etiquetas h1, p, o form.

Diseño atómico, átomos, moléculas, organismos y plantillas
Diseño atómico, átomos, moléculas, organismos y plantillas

Tomando como ejemplo nuestro sistema respiratorio y utilizándola como analogía a la multiplicación de números. Primero, en lugar de nariz, laringe y traquea para ingerir aire, tenemos elementos de entrada como inputs y botones para insertar los números.

En los pulmones se obtiene el oxígeno para crear una combustión interna para luego mandar al corazón sangre rica en oxígeno, también se despide el resultado de la combustión en forma de CO2. En la multiplicación, tenemos un componente Multiplicación de números que permite recibir dos números, multiplicarlos y mostrártelos en la aplicación a través de texto.

¿Cómo empezar a utilizar los componentes web?

Ahora, la creación de componentes web está basado en cuatro principales estándares de la web. Estos nos permite crear componentes reutilizables y encapsular su funcionamiento y al mismo tiempo permiten la comunicación entre ellos para crear sistemas robustos.

  • Custom elements o elementos personalizados
  • Shadow DOM, DOM en las sombras.
  • HTML Templates, Plantillas HTML.
  • <script type="module">, cargando módulos usando el tag script.
    • Antes se usaba HTML imports, importar componentes a través del tag <link rel="import">

Actualmente el soporte de estos cuatro estándares en los navegadores web es el siguiente. Tomado de https://www.webcomponents.org/.

Soporte de componentes web en navegadores
Soporte de componentes web en navegadores

La imagen fue tomada de webcomponents.org, y si quieres saber más a detalle sobre el soporte de los navegadores, aquí te dejo la lista:

También existen frameworks como Angular y Vue, y librerías como React, que se basan en componentes. Aunque no ocupen los cuatro estándares principales, se utilizan para crear aplicaciones web robustas con los mismos principios.

Aquí hay un compendio inicial de algunos de ellos:

Custom Elements Everywhere

Custom Elements Everywhere
Making sure frameworks and custom elements can be BFFs 🍻

en_USEN