En nuestro último artículo, discutimos las especificaciones de los componentes web (elementos personalizados, shadow DOM y plantillas HTML) en un alto nivel. En este artículo, y en los tres siguientes, pondremos estas tecnologías a prueba y las examinaremos con mayor detalle y veremos cómo podemos usarlas en producción hoy. Para hacer esto, crearemos un diálogo modal personalizado desde cero para ver cómo encajan las diversas tecnologías.
Serie de artículos:
- Introducción a los componentes web
- Elaboración de plantillas HTML reutilizables (esta publicación)
- Crear un elemento personalizado desde cero
- Encapsulación de estilo y estructura con Shadow DOM
- Herramientas avanzadas para componentes web
Plantillas HTML
Una de las características menos reconocidas, pero más poderosas, de la especificación de componentes web es la <template>
elemento. En el primer artículo de esta serie, definimos el elemento de plantilla como “plantillas definidas por el usuario en HTML que no se procesan hasta que se solicita”. En otras palabras, una plantilla es HTML que el navegador ignora hasta que se le indique lo contrario.
Estas plantillas se pueden distribuir y reutilizar de muchas formas interesantes. Para los propósitos de este artículo, veremos cómo crear una plantilla para un diálogo que eventualmente se usará en un elemento personalizado.
Definiendo nuestra plantilla
Por simple que parezca, un <template>
es un elemento HTML, por lo que la forma más básica de una plantilla con contenido sería:
<template>
<h1>Hello world</h1>
</template>
Ejecutar esto en un navegador resultaría en una pantalla vacía ya que el navegador no representa el contenido del elemento de la plantilla. Esto se vuelve increíblemente poderoso porque nos permite definir contenido (o una estructura de contenido) y guardarlo para más adelante, en lugar de escribir HTML en JavaScript.
Para utilizar la plantilla, necesitaremos JavaScript
const template = document.querySelector('template');
const node = document.importNode(template.content, true);
document.body.appendChild(node);
La verdadera magia ocurre en el document.importNode
método. Esta función creará una copia de la plantilla content
y prepárelo para insertarlo en otro documento (o fragmento de documento). El primer argumento de la función toma el contenido de la plantilla y el segundo argumento le dice al navegador que haga una copia profunda del subárbol DOM del elemento (es decir, todos sus elementos secundarios).
Podríamos haber usado el template.content
directamente, pero al hacerlo, habríamos eliminado el contenido del elemento y adjuntado al cuerpo del documento más tarde. Cualquier nodo DOM solo se puede conectar en una ubicación, por lo que los usos posteriores del contenido de la plantilla darían como resultado un fragmento de documento vacío (esencialmente un valor nulo) porque el contenido se había movido previamente. Utilizando document.importNode
nos permite reutilizar instancias del mismo contenido de plantilla en varias ubicaciones.
Luego, ese nodo se agrega al document.body
y renderizado para el usuario. En última instancia, esto nos permite hacer cosas interesantes, como proporcionar a nuestros usuarios (o consumidores de nuestros programas) plantillas para crear contenido, similar a la siguiente demostración, que cubrimos en el primer artículo:
Ver la pluma
Ejemplo de plantilla de Caleb Williams (@calebdwilliams)
en CodePen.
En este ejemplo, proporcionamos dos plantillas para representar el mismo contenido: autores y libros que han escrito. A medida que cambia el formulario, elegimos representar la plantilla asociada con ese valor. Usar esa misma técnica nos permitirá eventualmente crear un elemento personalizado que consumirá una plantilla que se definirá en un momento posterior.
La versatilidad de la plantilla
Una de las cosas interesantes de las plantillas es que pueden contener cualquier código HTML. Eso incluye guiones y elementos de estilo. Un ejemplo muy simple sería una plantilla que adjunta un botón que nos alerta cuando se hace clic en él.
<button id="click-me">Log click event</button>
Vamos a darle estilo:
button {
all: unset;
background: tomato;
border: 0;
border-radius: 4px;
color: white;
font-family: Helvetica;
font-size: 1.5rem;
padding: .5rem 1rem;
}
… y llámelo con un script realmente simple:
const button = document.getElementById('click-me');
button.addEventListener('click', event => alert(event));
Por supuesto, podemos juntar todo esto usando HTML <style>
y <script>
etiquetas directamente en la plantilla en lugar de en archivos separados:
<template id="template">
<script>
const button = document.getElementById('click-me');
button.addEventListener('click', event => alert(event));
</script>
<style>
#click-me {
all: unset;
background: tomato;
border: 0;
border-radius: 4px;
color: white;
font-family: Helvetica;
font-size: 1.5rem;
padding: .5rem 1rem;
}
</style>
<button id="click-me">Log click event</button>
</template>
Una vez que este elemento se agregue al DOM, tendremos un nuevo botón con ID #click-me
, un selector de CSS global dirigido al ID del botón y un detector de eventos simple que alertará sobre el evento de clic del elemento.
Para nuestro script, simplemente agregamos el contenido usando document.importNode
y tenemos una plantilla de HTML mayoritariamente contenida que se puede mover de una página a otra.
Ver la pluma
Plantilla con script y demostración de estilos de Caleb Williams (@calebdwilliams)
en CodePen.
Creando la plantilla para nuestro diálogo
Volviendo a nuestra tarea de crear un elemento de diálogo, queremos definir el contenido y los estilos de nuestra plantilla.
<template id="one-dialog">
<script>
document.getElementById('launch-dialog').addEventListener('click', () => {
const wrapper = document.querySelector('.wrapper');
const closeButton = document.querySelector('button.close');
const wasFocused = document.activeElement;
wrapper.classList.add('open');
closeButton.focus();
closeButton.addEventListener('click', () => {
wrapper.classList.remove('open');
wasFocused.focus();
});
});
</script>
<style>
.wrapper {
opacity: 0;
transition: visibility 0s, opacity 0.25s ease-in;
}
.wrapper:not(.open) {
visibility: hidden;
}
.wrapper.open {
align-items: center;
display: flex;
justify-content: center;
height: 100vh;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
visibility: visible;
}
.overlay {
background: rgba(0, 0, 0, 0.8);
height: 100%;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
}
.dialog {
background: #ffffff;
max-width: 600px;
padding: 1rem;
position: fixed;
}
button {
all: unset;
cursor: pointer;
font-size: 1.25rem;
position: absolute;
top: 1rem;
right: 1rem;
}
button:focus {
border: 2px solid blue;
}
</style>
<div class="wrapper">
<div class="overlay"></div>
<div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content">
<button class="close" aria-label="Close">✖️</button>
<h1 id="title">Hello world</h1>
<div id="content" class="content">
<p>This is content in the body of our modal</p>
</div>
</div>
</div>
</template>
Este código servirá como base para nuestro diálogo. Al desglosarlo brevemente, tenemos un botón de cierre global, un encabezado y algo de contenido. También hemos agregado un poco de comportamiento para alternar visualmente nuestro diálogo (aunque aún no es accesible). Desafortunadamente, los estilos y el contenido de la secuencia de comandos no están dentro del alcance de nuestra plantilla y se aplican a todo el documento, lo que da como resultado comportamientos menos que ideales cuando se agrega más de una instancia de nuestra plantilla al DOM. En nuestro próximo artículo, utilizaremos elementos personalizados y crearemos uno propio que consume esta plantilla en tiempo real y encapsula el comportamiento del elemento.
Ver la pluma
Diálogo con plantilla con guión de Caleb Williams (@calebdwilliams)
en CodePen.
Serie de artículos:
- Introducción a los componentes web
- Elaboración de plantillas HTML reutilizables (esta publicación)
- Crear un elemento personalizado desde cero
- Encapsulación de estilo y estructura con Shadow DOM
- Herramientas avanzadas para componentes web