soy Kseso y esto EsCSS

Generando ventanas modales con Javascript puro y su CSS al gusto

Artículo de Autor invitado: Jorge MG muestra cómo generar modales con sólo javascript y así, a diferencia de las nativas del navegador, poder aplicar el CSS deseado.

Generando ventanas modales con Javascript puro y su CSS al gusto

✎ 5
COLABORACIÓN AUTOR INVITADO

Nota del Editor: El post que te dispones a leer es obra de Jorge MG publicado en EsCss como artículo de Autor Invitado. En él encontrarás la forma de crear modales con el mínimo de javascript puro (sin necesidad de librería alguna) y así poder aplicar los estilos que creas convenientes o necesites.
Como todos los artículos de autoría ajena queda fuera de la licencia Beerware del blog. Cualquier aspecto relacionado con ello has de tratarlo directamente con su autor.
Tras una apropiación indebida por parte de alguien, este post y el script están como
Copyright (C) 2016 Jorge Mg (@itsjorgemg en Twitter)

ventanas modales

Las funciones de JavaScript alert(), confirm() y prompt() son muy útiles para mostrar o pedir datos en el momento de desarrollar una página web. Eso sí, en el momento de producción, su principal inconveniente es que su diseño y estilo depende del navegador, y no podemos modificarlo.

Para solventar este aspecto, vamos a crear nuestra propia ventana modal con JavaScript con el mismo funcionamiento que las generadas por el navegador, pero con la ventaja de poder añadir estilos vía CSS.

Si buscas por Internet, seguro que encontrarás miles de realizaciones como esta, pero es posible que dependan de otras librerías o tengan multitud de opciones que nunca vas a usar. Así que el objetivo de este artículo es saber cómo montar tu propia ventana modal, paso a paso, con el mínimo código posible (1 única función JavaScript) y con las opciones que necesites.

Si quieres que algo salga tal y como tú quieres, hazlo tú mismo.

Por cierto, si echas en falta alguna opción, o decides ampliar esta demo, te animo a que lo cuentes en un comentario.

Parámetros y opciones de la ventana

Antes de empezar a escribir el código, hay que plantear las funciones que podrá hacer. De esta forma, para ejecutar la función que mostrará la ventana habrá que indicar los siguientes parámetros:

  • id: un identificador para poder incluir una ventana dentro de otra
  • data: un objeto JSON con las siguientes propiedades:
    • title: título de la ventana que se mostrará en su cabecera
    • width: ancho del panel, en píxeles
    • height: altura del panel, en vh. Por ejemplo, el valor 50 hará que la altura de la modal sea la mitad que la de la pantalla (viewport). Tanto el ancho como el alto estarán controlados desde CSS para impedir que la modal sea más grande que el viewport, sea cual sea el valor indicado en estas propiedades
    • content: mensaje que mostrará la ventana. Puede ser código HTML. Desde CSS podremos especificar algunas reglas para cuando se muestre únicamente una imagen o un iframe en la modal
  • ok: opcionalmente, podemos mostrar un botón del tipo "Aceptar" u "OK". Este parámetro deberá ser un array, cuyo primer ítem será el título del botón y el segundo, una función que se ejecutará al pulsar el botón
  • cancel: opcional, con el mismo funcionamiento que el anterior, pero del tipo "Cancelar". También debe ser un array del tipo: [nombreBoton, funcionJS]
  • input: opcional, para mostrar un cuadro de texto, de la misma forma que la función prompt(). Este parámetro deberá ser un array de un único elemento: el placeholder del input. Para que se pueda mostrar este input, es necesario indicar el parámetro "ok", ya que el texto escrito se recibirá como parámetro de la función

Generando la ventana y sus botones

Con este planteamiento, ya podemos empezar. La modal no estará escrita en el HTML, así que primero de todo habrá que crearla vía JavaScript. Después de abrirse una vez, la modal ya existirá y por lo tanto, no hará falta volverla a generar. Comento el código para ir indicando paso a paso qué es lo que estamos haciendo.

data=data || {}; // si no existe data, creamos un objeto vacío para evitar posteriores errores id="modal-"+id; // añadimos "modal-" a la id para evitar conflictos con otros elementos if (document.getElementById(id)==null) { // solo hace falta crearla si no existe var d=document.createElement("div"); d.className="jmgmodal"; // clase para estilizarla vía CSS d.id=id; // creamos el panel interior var p=document.createElement("div"); p.className="panel"; // creamos los componentes de la cabecera: título y botón de cerrar var t=document.createElement("div"); t.className="title"; var cl=document.createElement("div"); cl.className="close"; cl.innerHTML='×'; // cerramos y vaciamos la modal al pulsar el botón X cl.addEventListener('click',function(ev) { ev.preventDefault(); var dTop=this.parentNode.parentNode; dTop.classList.remove("visible"); dTop.querySelector(".panel .content").innerHTML=''; }); // creamos la caja donde se mostrará el contenido var ct=document.createElement("div"); ct.className="content"; // también añadimos un pie, para añadir los posibles botones var f=document.createElement("div"); f.className="footer"; /* finalmente, añadimos "t", "cl", "ct" y "f" (título, botón cerrar, div contenido y div footer) a "p" (panel interior), éste lo añadimos a "d" (div principal, para oscurecer el fondo), y "d" lo añadimos al body de la página */ p.appendChild(t);p.appendChild(cl);p.appendChild(ct);p.appendChild(f); d.appendChild(p); document.body.appendChild(d); }

Básicamente consiste en jugar con document.createElement() y elmt.appendChild() junto con los atributos de cada elemento para conseguir la estructura deseada.

Ya tenemos la ventana creada. Ahora hay que rellenar todos sus componentes con los datos correspondientes.

// guardamos cada componente en una variable var mod=document.getElementById(id), p=mod.querySelector(".panel"), t=mod.querySelector(".panel .title"), ct=mod.querySelector(".panel .content"), f=mod.querySelector(".panel .footer"); if (f==null) { // puede ocurrir que el footer no exista, así que lo volvemos a crear mod.classList.remove("nofooter"); var f=document.createElement("div"); f.className="footer"; p.appendChild(f); } // rellenamos los datos t.innerHTML=data.title || ''; ct.innerHTML=data.content || ''; f.innerHTML=''; // comprobamos que el número es válido antes de añadirlo if (!isNaN(data.width)) p.style.maxWidth=data.width+'px'; if (!isNaN(data.height)) p.style.maxHeight=data.height+'vh';

De esta parte, hay que destacar un par de aspectos. El primero es el uso de variable=valor || ''. Esto asigna el valor a la variable, pero, en caso de que dicho valor sea inválido (null o undefined), se usará el siguiente valor indicado después de la doble barra vertical. En este caso, se quedará en blanco, pero sería recomendable cambiarlo por el título de tu página, por ejemplo.

El segundo aspecto es el uso de la función isNaN(). Esta función devuelve true si el parámetro indicado no es un número (ya que NaN significa Not A Number). Por lo tanto, al negarlo sabemos que la condición solo se ejecutará cuando el valor del alto y el ancho sea válido.

A continuación, seguimos añadiendo los botones que aparecerán en el footer:

// si es necesario, creamos el botón "Aceptar" if (ok && ok.length>1) { // la variable "param" se usará para devolver el valor del input // si no hubiera input, el valor será null var param={value:null}; // si es necesario, creamos un párrafo nuevo con su input if (input && input.length>0) { var ph=document.createElement("p"); ph.className="action"; var txt=document.createElement("input"); txt.className="action"; txt.setAttribute("placeholder",input[0]); txt.addEventListener('keydown',function(ev) { // pulsar "enter" es equivalente a pulsar "aceptar" if (ev.keyCode==13 || ev.key=="Enter") { ev.preventDefault(); mod.classList.remove("visible"); ok[1](param.value); } }); // añadimos el input al párrafo, y éste al contenido ph.appendChild(txt); ct.appendChild(ph); // guardamos la referencia al input param=ct.querySelector("p.action > input.action"); /* ponemos el foco al input, pero esperamos unos milisegundos para que se genere */ setTimeout(function(){ param.focus(); },100); } // creamos el botón OK var bOk=document.createElement("button"); bOk.className="action"; bOk.innerHTML=ok[0]; bOk.addEventListener('click',function(ev) { /* al pulsar en él, se cierra la ventana y se ejecuta la función indicada, devolviendo el valor del input (si existe) o null */ ev.preventDefault(); mod.classList.remove("visible"); ok[1](param.value); }); // añadimos el botón al footer f.appendChild(bOk); }

Hasta aquí ya tenemos el botón "Aceptar" y el input, si fuera necesario crearlos. Como el cuadro de texto no forma parte de ningún formulario, al pulsar Intro, no pasará nada. Esto lo solucionamos añadiendo el evento keydown, donde comprobamos si la tecla pulsada es el Intro, y en ese caso cerramos la ventana y ejecutamos la función correspondiente al botón OK, pasándole como parámetro el valor del input.

Además, para comodidad del usuario, ponemos el foco en el input nada más abrir la ventana. Le ponemos un retraso de 100 milisegundos (valor totalmente arbitrario) para que dé tiempo a que la ventana esté creada y abierta, puesto que si lo hacemos immediatamente el elemento aún no está visible en la página.

Al ejecutar la función del botón OK, le pasamos el parámetro param.value. Si no hay input, este valor será null, mientras que cuando sí lo haya, el valor corresponderá al texto introducido. De esta forma, no tenemos que preocuparnos de controlar si hay que pasar o no el parámetro y evitamos posibles errores.

El código para crear el botón Cancelar es muy similar al anterior, pero el hecho de no usar ningún parámetro lo hace más simple.

// hacemos lo mismo con el botón Cancelar, si existe if (cancel && cancel.length>1) { var bCancel=document.createElement("button"); bCancel.className="action"; bCancel.innerHTML=cancel[0]; bCancel.addEventListener('click',function(ev) { ev.preventDefault(); mod.classList.remove("visible"); cancel[1](); }); f.appendChild(bCancel); } // si no hay ningún botón, se elimina el footer if (f.innerHTML=='') { p.removeChild(f); mod.classList.add("nofooter"); } /* esperamos unos milisegundos para que se genere, y añadimos la clase .visible para mostrarla desde CSS */ setTimeout(function(){ mod.classList.add("visible"); },50);

Finalmente, hay que comprobar si se han creado los botones. Si no es así, eliminamos el footer, ya que no se va a usar, y lo indicamos con la clase "nofooter", que usaremos desde CSS. Hecho esto, solo queda añadir la clase "visible", que mostrará la ventana. El motivo por el que lo retrasamos 50 milisegundos es, igual que antes con el input, porque esperamos a que la ventana se haya generado.

Demo y consideraciones finales

Solo falta meter todo el código en una función, que ejecutaremos cuando queramos abrir la ventana. Este es el resultado y demo de las opciones de las que dispone la ventana modal:

See the Pen jmgModal window by Kseso (@Kseso) on CodePen.

En la parte del CSS no hay nada nuevo a destacar (si acaso, se admiten mejoras de diseño...). Básicamente, lo más importante es la clase .visible, que controlará la apertura de la ventana. Los selectores usados se pueden optimizar, pero he preferido dejarlos así para una mejor comprensión del elemento al que apuntan.

Sobre el funcionamiento de la ventana, he optado por mantener siempre operativo el botón X de cierre para que sea lo menos intrusiva para el usuario.

Jorge Mg
Artículo original de

Jorge Mg

Descubriendo, experimentando y dominando Html, Css y Js
Puedes contactar con Jorge aquí mismo en los comentarios o en sus rr.ss.:
Twitter
Codepen