Una ventana modal (modal window) o cuadro de dialogo (dialog box) es el elemento de la interfaz de usuario que usamos cuando queremos centrar la atención del usuario en un elemento en particular que esta ligado a alguna acción en concreto, este elemento se situara por encima del demás contenido.
Por otro lado un lightbox es el efecto de oscurecer todo el contenido en nuestra interfaz a excepción de un único elemento que se busca destacar por sobre las demás cosas.
Aunque una ventana modal no tiene que ir siempre acompañada del efecto lightbox en esta ocasión si que usaremos ambas cosas.
Maquetando la estructura base
Antes de ponernos a generar código HTML desde Javascript nos viene bien primero escribir a mano la estructura para tenerla de referéncia.
<div class="liwindow-container">
<div class="liwindow-card">
<div class="liwindow-header">
<h4>Estas a punto de eliminar un archivo</h4>
<button type="button" class="liwindow-close">×</button>
</div>
<div class="liwindow-body">Realmente deseas continuar?</div>
<div class="liwindow-footer">
<button type="button" class="liwindow-btn liwindow-cancel">Cancelar</button>
<button type="button" class="liwindow-btn liwindow-acept">Aceptar</button>
</div>
</div>
</div>
Usaremos un contenedor general (.liwindow-container) que sera el que oculte todo el demás contenido de la web mostrandose semiopaco.
Dentro pondremos el cuadro (.liwindow-card) que contendrá los elementos de nuestra ventana modal.
Para el caso de las ventanas tipo confirm() usaremos un header (.liwindow-header) donde pondremos el titulo de la ventana modal y el botón de cierre (.liwindow-close).
Después viene el contenedor del cuerpo de la ventana (.liwindow-body) y por ultimo el pie (.liwindow-footer) donde pondremos los botones (.liwindow-btn) para aceptar (.liwindow-acept) o cancelar (.liwindow-cancel).
Aplicando los estilos
/* -- liwindow -- */
.liwindow-container *, .liwindow-container *::after, .liwindow-container *::before{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.liwindow-container{
position: fixed;
top: 0;
left: 0;
background: rgba(0,0,0,0.5);
width: 100%;
height: 100%;
z-index: 200;
}
/* -- liwindow Modal Dialog -- */
.liwindow-card{
margin: 0 auto;
margin-top: 40px;
width: 500px;
max-width: 100%;
box-shadow: 0px 0px 25px -5px #333;
}
.liwindow-header, .liwindow-footer, .liwindow-body{
background-color: #fff;
}
.liwindow-header{
display: flex;
flex-direction: row;
justify-content: space-between;
}
.liwindow-close{
padding: 20px;
display: inline-block;
font-size: 1.33rem;
color: #333;
font-weight: bold;
background-color: transparent;
border: none;
transition: 0.3s all;
}
.liwindow-close:hover, .liwindow-close:focus{
color: #7C4DFF;
background-color: #f3f3f3;
}
.liwindow-close:active{
color: #7C4DFF;
transform: translate(-3px, -3px);
transform: scale(0.9);
}
.liwindow-close:focus{
outline: 1px solid #00BCD4;
}
.liwindow-body{
padding: 20px;
color: #333;
font-size: 18px;
}
.liwindow-header h4{
color: #333;
padding: 20px;
display: inline-block;
font-size: 25px;
font-weight: bold;
}
.liwindow-footer{
text-align: right;
}
.liwindow-btn{
padding: 20px;
background-color: transparent;
border: none;
color: #9c27b0;
text-transform: uppercase;
font-size: 15px;
transition: 0.3s all;
}
.liwindow-btn:hover, .liwindow-btn:focus{
color: #7C4DFF;
background-color: #f3f3f3;
}
.liwindow-btn:active{
color: #7C4DFF;
transform: translate(-3px, -3px);
transform: scale(0.9);
}
.liwindow-btn:focus{
outline: 1px solid #00BCD4;
}
.liwindow-hide{
display: none;
}
Aplicando estos estilos CSS anteriores a nuestra estructura HTML tendríamos la siguiente apariencia:
Definiendo las clases y métodos
/* -- Clase principal para el modal window -- */
class liwindow {
constructor(html)
/* Metodo para abrir lightbox */
show()
/* Metodo para cerrar lightbox*/
hide()
/* Metodo para agregar contenido al lightbox*/
setContent()
/* Metodo para remover el lightbox y su contenido del html*/
remove()
}
/* -- Alert Modal Window -- */
class lialert extends liwindow {
}
/* -- Confirm Modal Window -- */
class liconfirm extends liwindow {
}
Partiremos de una clase general con los métodos necesarios para el funcionamiento de nuestras ventanas modales, crearemos una clase heredera por cada elemento con el efecto lightbox que deseemos. En este caso una clase para nuestros custom alert() y otra para nuestros custom confirm().
Lógica de la clase principal
/* -- Clase para lightbox y modal dialogs -- */
class liwindow {
constructor(html) {
/* Creamos el contenedor principal del lightbox */
this.container = document.createElement("div");
this.container.setAttribute("class", "liwindow-container liwindow-hide");
this.container.addEventListener("click", this.hide);
document.body.appendChild(this.container);
/* Añadimos el html proporcionado */
if (html != null) this.setContent(html);
}
/* Metodo para abrir lightbox */
show = () => {
this.container.classList.remove("liwindow-hide");
document.body.style.overflow = "hidden";
};
/* Metodo para cerrar lightbox */
hide = () => {
this.container.classList.add("liwindow-hide");
document.body.style.overflow = "auto";
};
/* Metodo para agregar contenido al lightbox */
setContent = (cont) => {
if (typeof cont == "string") {
this.container.innerHTML = cont;
this.container.firstChild.addEventListener("click", function (e) {
e.stopPropagation();
});
} else {
cont.addEventListener("click", function (e) {
e.stopPropagation();
});
this.container.appendChild(cont);
}
};
/* Metodo para remover el lightbox y su contenido del html */
remove = () => document.body.removeChild(this.container);
}
Desde el constructor principal generaremos parte del HTML previamente descrito y lo insertaremos en el documento html, pero este contenido estará oculto inicialmente (usando la clase: .liwindow-hide).
Para añadir contenido a nuestra ventana usamos el método setContent(), el contenido dependerá del componente final (alert() o confirm()), si estamos añadiendo texto plano este se insertara con innerHTML, de lo contrario se añadirá con appendChild.
Nuestra ventana modal estará oculta hasta que se mande a llamar el método show(), el cual quitara la clase que mantiene oculto el contenido, ademas de modificar la propiedad overflow del body del documento para evitar que nuestra ventana modal sea evadida al hacer scroll.
El método hide() por otro lado se encargara de volver a ocultar la ventana modal, añadiendo la clase .liwindow-hide y devolviendo el overflow del body a su estado original.
Desde el inicio estamos enlazando el método hide() a nuestro contenedor principal (.liwindow-container), de modo que al hacer click sobre el automáticamente se cierre la ventana modal, pero para evitar que inesperadamente se cierre al hacer click al contenido de nuestro cuadro de dialogo debemos usar stopPropagation en dicho contenido.
Lógica de nuestro custom alert
/* -- Alert Modal Window -- */
class lialert extends liwindow {
constructor(body) {
const cont = `<div class="liwindow-card">
<div class="liwindow-body">${body}</div>
<div class="liwindow-footer">
<button type="button" class="liwindow-btn liwindow-acept">Aceptar</button>
</div>
</div>`;
super(cont);
this.container
.querySelector(".liwindow-acept")
.addEventListener("click", this.hide);
}
}
La lógica de nuestro alert es muy simple, solo estamos definiendo el HTML que llevará el cuadro de dialogo concatenando el mensaje que se mostrará, y todo este código lo mandamos al contenedor principal del lightbox pasandolo como argumento del constructor de la clase principal.
Ya que se haya insertado el HTML, al botón que ahí mismo definimos le enlazamos el funcionamiento del método hide().
Lógica de nuestro custom confirm
/* -- Confirm Modal Window -- */
class liconfirm extends liwindow {
constructor(param) {
const cont = `<div class="liwindow-card">
<div class="liwindow-header">
<h4>${param.header}</h4>
<button type="button" class="liwindow-close">×</button>
</div>
<div class="liwindow-body">${param.body}</div>
<div class="liwindow-footer">
<button type="button" class="liwindow-btn liwindow-cancel">Cancelar</button>
<button type="button" class="liwindow-btn liwindow-acept">Aceptar</button>
</div>
</div>`;
super(cont);
this.container
.querySelector(".liwindow-close")
.addEventListener("click", this.hide);
this.container
.querySelector(".liwindow-cancel")
.addEventListener("click", () => {
this.hide();
param.action(false);
});
this.container
.querySelector(".liwindow-acept")
.addEventListener("click", () => {
this.hide();
param.action(true);
});
}
}
La lógica de nuestro confirm es similar a la anterior, solo que aquí tenemos más HTML, exactamente el mismo HTML que maquetamos al inicio de todo, ademas de que en el caso anterior teníamos un solo botón y ahora tenemos 3.
Primero enlazamos el método hide() tanto a nuestro botón de cerrar.
Después enlazamos un callback tanto al botón botón Aceptar, como al botón de cancelar, con la diferencía de que pasaremos como argumento true o false según la acción seleccionada por el usuario.
Modo de uso
Solo debemos enlazar el código CSS y JS a nuestro documento HTML para empezar a crear tantas instancias de nuestras ventanas modales como necesitemos.
const myalert = new lialert("Una alerta simple");
Para para poder abrir nuestra ventana modal solo necesitamos llamar al método show(), esto lo podemos hacer desde cualquier otra parte de nuestro código javascript, por ejemplo al hacer click a algún botón.
document.querySelector("#showalert").addEventListener("click", myalert.show);
Lo mismo para nuestro confirm, con la diferencía de que debemos pasar como argumento un objeto que contenga todo lo que mostraremos en el cuadro de dialogo del confirm.
const myconfirm = new liconfirm({
header: "Estas a punto de eliminar un archivo",
body: "Realmente deseas continuar?",
action: (result) => {
/* Eliminar archivo en caso de aceptar */
}
});
A continuación mostrare el script final y ejemplos de funcionamiento, todo esto estará en Codepen por si deseas exportarlo e incluso modificarlo.
Código final y resultado
See the Pen Custom Alert/Confirm Vanilla JS by Ulises Rendon (@fdulises) on CodePen.