JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Creando diálogos y pop-ups nativos con HTML: El elemento "dialog" de HTML 5.2

Una necesidad común en casi cualquier aplicación Web es la de mostrar diálogos al usuario. Se trata de los típicos "pop-ups" que surgen en la aplicación de vez en cuando con múltiples propósitos: notificaciones, preguntar algo al usuario, mostrar los detalles de un registro en una base de datos... Casi cualquier cosa que se te pueda ocurrir.

Por ejemplo, cuando GMail te saca las opciones para establecer la densidad de la información a mostrar en tu bandeja de entrada:

Diálogo de ejemplo de GMail

Eso es un diálogo. Para ser exactos además, es un diálogo modal ya que oscurece todo lo que hay debajo y no te permite interactuar con nada más hasta que lo cierres.

Crear algo como esto mediante el uso de capas y un poco de JavaScript no es muy complicado, pero tiene varias "pegas":

  1. Lo tenemos que crear desde cero o bien usar un plugin de jQuery o similar que nos lo de hecho.
  2. Añade "peso" a nuestra página, y es un componente más a mantener.
  3. No tenemos una forma estandarizada de crearlos, mostrarlos y darle un aspecto predefinido.
  4. ¿Qué pasa si el usuario muestra varios diálogos al mismo tiempo? ¿Cuál se vería y por qué?
  5. ¿Es accesible para personas con discapacidad y cumple con los estándares en este aspecto?.

Es por ello que, a finales de 2017, el consorcio de la Web (W3C) lanzó el elemento <dialog> dentro del estándar HTML 5.2.

Este nuevo elemento añadido a la familia de HTML5 trata de solucionar muchos de esos problemas que comentaba hace un momento. Entre otras cosas nos proporciona:

  • Un elemento semántico específico para contenido secundario de cara al usuario.
  • Dos versiones de diálogos: modal y convencional.
  • Una API súper-sencilla para mostrarlo y ocultarlo.
  • Soporte para formularios (esto mola mucho, lo veremos ahora mismo).
  • Posibilidad de personalizar la forma de oscurecer el fondo en los modales.
  • Gestión automática de varios diálogos simultáneos.
  • Soporte para accesibilidad por medio de un par de nuevos roles ARIA (dialog y alertdialog).

A pesar de todas estas cosas, como veremos, es un elemento extremadamente sencillo de utilizar.

Vamos a darle un repaso...

Definiendo y mostrando diálogos convencionales

Para definir un diálogo dentro de la página o aplicación actual, solo tenemos que introducir el HTML que nos interese dentro de un elemento de tipo <dialog>, así:

<dialog id="DialogoNotificacion">
    <h2>¡ATENCIÓN!</h2>
    <p>Se ha producido un problema en la aplicación.</p>
</dialog>

Este elemento no es visible en la página ya que está pensado para mostrarse sólo cuando sea necesario. De hecho, estos son los estilos por defecto que le asigna el navegador y que es interesante conocer:

Estilos por defecto asignados por el navegador

Para mostrarlo lo único que tenemos que hacer es localizar el elemento y utilizar el método show() del objeto del DOM que se corresponde con el elemento anterior, es decir, algo así:

var dialogo = document.getElementById('DialogoNotificacion');
dialogo.show();

Esto hará que el contenido del diálogo se muestre inmediatamente.

Si te fijas en la figura anterior, el estilo por defecto es display:block, pero la regla dialog:not(open]) indica que no se muestre. Esto nos da una pista muy interesante sobre cómo funciona: el método show() lo único que hace es ponerle un atributo open al elemento HTML, haciendo que se muestre por no entrar en juego esta regla por defecto. Si observamos en el explorador del DOM qué ocurre cuando lo mostramos y lo ocultamos, veremos que aparece y desaparece ese atributo:

El atributo open aparece y desaparece

Si nos fijamos de nuevo en los estilos por defecto, vemos que no tiene un tamaño definido (se adapta al tamaño de su contenido) y además tiene posicionamiento absoluto establecido, posicionado por defecto par ocupar todo el ancho ya que tiene left:0px; y right:0px;. Como también tiene un margen automático, si le damos un ancho fijo (o uno máximo, mejor) y una posición vertical, lo podemos colocar donde queramos:

dialog[open] {
    position: fixed;
    width: 50%;
    max-width: 750px;
    top: 10px;
}

De este modo cuando esté abierto ocupará siempre la mitad del viewport con un máximo de 750px, y estará ubicado en la parte de arriba de la página con una pequeña separación.

Un detalle a tener en cuenta es que, si la página es muy larga y hay scroll vertical el diálogo desaparecería del viewport al desplazarnos. Por eso, si le cambiamos el estilo position de absolute (por defecto) a fixed, como he hecho yo, conseguiremos que siempre esté ubicado en esa posición.

Para cerrar el diálogo que acabamos de abrir es necesario quitarle ese atributo open, lo cual se consigue llamando al método close(). Así, podemos hacer que en cuanto el usuario pulse en un botón, en cualquier parte del diálogo o al cabo de un tiempo máximo, se cierre. Por ejemplo, para que se cierre al pulsar en cualquier parte del mismo, sólo tendríamos que hacer:

dialogo.addEventListener('click', () => dialogo.close());

o en sintaxis JavaScript más tradicional (sin usar una arrow function de ECMAScript como he hecho yo):

dialogo.addEventListener('click', function(){
    dialogo.close();
});

Una cosa importante a tener en cuenta es que este diálogo se va a mostrar por encima de todos los demás elementos independientemente del z-index que tengan asignados todos ellos. Luego veremos un poco más en detalle esto y los posibles conflictos con otros diálogos. Pero antes...

Diálogos modales

Los diálogos que hemos mostrado hasta ahora son solo una capa por encima del resto de la página que podríamos haber conseguido de manera sencilla con un simple <div>, aunque algo de trabajo nos ahorra.

Más interesante es poder lanzar diálogos modales. Es decir, aquellos que además de mostrarse por encima de los demás impiden interactuar con la página hasta que los cerremos. Es más, suelen meter algún tipo de elemento que oscurece o difumina los elementos que tiene por debajo. Suelen ser los más interesantes.

Esto está contemplado también en el caso del elemento <dialog> y para mostrar uno de estos lo único que tenemos que hacer es llamar al método showModal():

var dialogo = document.getElementById('DialogoNotificacion');
dialogo.showModal();

Esto tiene dos efectos importantes:

  1. El diálogo añade un fondo semitransparente por detrás que tapa lo que hay debajo, indicando que no se puede tocar nada excepto el diálogo en sí.
  2. Es posible cerrarlo automáticamente pulsando la tecla ESC de tu teclado, como un diálogo modal del sistema.

En esta figura se ve el aspecto del mismo diálogo mostrado de la manera convencional y de la manera modal:

Diferencia entre un diálogo convencional y uno modal

Los diálogos modales, además, definen un pseudo-elemento CSS llamado backdrop que nos permite personalizar cómo va a ser ese fondo semitransparente que saca, si queremos cambiarle aspecto por defecto. Desde algo tanto tonto como cambiarle el color y el grado de transparencia:

dialog::backdrop {
    background-color: rgba(255,0,0,0.8);
}

hasta crear efectos más potentes con gradientes, modos de mezcla y otras características avanzadas de CSS3:

dialog::backdrop {
    background: linear-gradient(90deg, rgb(20%, 40%, 60%), rgb(60%, 20%, 40%));
    mix-blend-mode: darken;
}

que se vería así:

Backdrop personalizado

La imaginación (y el buen/mal gusto de cada uno) es el límite 😉

Gestión de formularios dentro de diálogos HTML 5.2

Otra de las cuestiones muy interesantes que tienen estos diálogos nativos es que pueden trabajar automáticamente con formularios dentro de ellos y devolver respuestas de los usuarios al JavaScript de la página.

Para ello se define un nuevo método para los formularios, que solo sirve para cuando están dentro de un diálogo modal. Se trata del método dialog. Por ejemplo, fíjate en este diálogo con un formulario en su interior:

<dialog id="dialogoFormulario">
    <form method="dialog">
        <p>¿De verdad quieres hacerlo?</p>
        <button type="submit" value="no">Ni de broma</button>
        <button type="submit" value="sí">¡Por supuesto!</button>
    </form>
</dialog>

Seguramente ya te has dado cuenta de que tiene dos botones de envío del formulario (dos submit). Cuando tenemos un formulario dentro de un diálogo y pulsamos un botón de tipo submit, éste no se envía, sino que lo que ocurre es que el diálogo se cierra automáticamente y se notifica de este cierre a través del evento close del diálogo, que salta cada vez que se cierra uno.

Cuando un diálogo se ha cerrado y tiene dentro un formulario podemos leer el valor del botón submit utilizado mediante la propiedad returnValue.

Entonces, en el formulario que acabamos de ver podríamos escribir:

var dialogoPregunta = document.getElementById('dialogoFormulario');
dialogoPregunta.addEventListener('close', () =>
        alert('Has contestado que '+ dialogoPregunta.returnValue)
    );

Como en returnValue lo que viene es el contenido del atributo value del botón pulsado, veremos algo como esto al utilizarlo:

Uso de un diálogo con un formulario

Una cuestión importante es ¿qué pasa cuando cancelamos el diálogo modal con ESC y tiene dentro un formulario?. Pues que se envía el que tenga el foco en ese momento o sino una cadena vacía.

Si el formulario tuviese campos y nos interesan, lógicamente, sus valores, lo único que tendríamos que hacer es leeros mediante JavaScript en el evento close ya que el hecho de cerrar el diálogo no elimina el contenido ni lo cambia. Sigue estando ahí solo que oculto y por lo tanto accesible desde el DOM.

De hecho esto tiene el efecto adicional de que si volvemos a abrir el mismo diálogo con el formulario seguirán los mismo valores que se metieron la última vez, por lo que deberíamos vaciarlos y poner valores por defecto si lo que pretendemos es reutilizar el mismo formulario para una nueva acción.

Coincidencia de diálogos: la pila de diálogos

Una cuestión interesante a la hora de mostrar diálogos no modales es ¿qué ocurre cuando mostramos varios uno detrás de otro?. Es decir, dado que los diálogos convencionales no muestran el bloqueo de otros diálogos y es perfectamente posible abrir varios distintos a la vez: ¿cómo se muestran en este caso si su posición hace que se superpongan?

Pues muy sencillo: al navegador se encarga de mostrarlos uno encima de otro en el orden en el que se fueron lanzando, de modo que no interfieren y además no se tiene en cuenta en absoluto su propiedad z-index. Esto es muy interesante y nos ahorra problemas porque podemos tener la seguridad de que se pueden ir superponiendo y a medida que se van cerrando van apareciendo los que estén pendientes. Otra gran ventaja de usar este método y no un plugin cualquiera de los que hay por ahí.

Soporte de navegadores

Aunque hace ya año y medio que está definido la especificación, en el momento de escribir esto el soporte de los navegadores se reduce Chrome (y los que usan Chromium por debajo como Opera o Brave).

Firefox lo tiene en el estado "en implementación" desde hace ¡6 años! (mucho antes ya de que estuviese cerrada la especificación) pero no lo terminan de implementar. De hecho lo puedes activar en Firefox yendo a about:config y buscando la propiedad dom.dialog_element.enabled, pero no te lo recomiendo porque está a medio hacer y funciona fatal.

¿Significa esto que es mejor no utilizarlo?

No. De hecho te recomiendo que lo utilices. La mayor parte de los navegadores acabarán de implementarlo en breve seguramente, y quizá en unos meses el soporte sea universal. Pero es que además la propia Google ha creado un polyfill para dialog que va de maravilla y que puedes utilizar.

Solo tienes que añadir el archivo .css (que encontrarás en la carpeta dist de ese repositorio) arriba de todo de la página (de primero para poder sobrescribirlo), y el archivo dialog-polyfill.js después de todos los diálogos que hayas definido (el mejor sitio: antes del cierre del </body>). Una vez hecho esto solo debes activar la funcionalidad en todos los elementos dialog así:

var dialogos = document.getElementsByTagName('dialog');
for(var n=0; n<dialogos.length; n++) {
    var dialogo = dialogos[n];
    dialogPolyfill.registerDialog(dialogo);
}

es decir, se buscan todos los diálogos y se activan para que funcionen como es de esperar llamando al método registerDialog() del polyfill. Por supuesto, si la funcionalidad está ya soportada, esto no tiene efecto alguno.

Pruébalo en Firefox o incluso en una versión vieja de Internet Explorer y verás que funciona bien 😊

En resumen

El elemento dialog es un añadido muy interesante a HTML5 que te permitirá definir y gestionar diálogos de manera estandarizada y sencilla. Te recomiendo que empieces a utilizarlos.

Te he dejado un ejemplo para descargar (ZIP, 14.2Kb) con todo lo que explico aquí ya implementado, para que puedas experimentar y probarlo todo bien.

¡Espero que te resulte útil!

José Manuel Alarcón José Manuel Alarcón
Fundador de campusMVP.es, el proyecto de referencia en formación on-line para programadores en lengua española. Autor de varios libros y cientos de artículos. Galardonado como MVP de Microsoft desde 2004. Gallego de Vigo, amante de la ciencia y la tecnología, la música y la lectura. Ayudando a la gente en Internet desde 1996.
Descarga GRATIS mi último libro (no técnico): "Tres Monos, Diez Minutos".
Banner

Agregar comentario