soy Kseso y esto EsCSS

Javascript con furoya: construcción de calendarios con date

El objeto de javascript date() y algunas de sus particularidades usadas para construir calendarios con diferentes métodos.

Javascript con furoya: construcción de calendarios con date

✎ 2
COLABORACIÓN AUTOR INVITADO
Javascript con furoya: construcción de calendarios con date

Hace algún tiempo mencionamos al objeto Date(), y no profundizamos demasiado, porque su uso es bastante simple : permite capturar la fecha actual, asignar una fecha a elección, obtener diferencias de tiempo entre dos fechas, calcular el tiempo UTC, ...

Existen innumerables tutoriales para probarlo, aunque hay algunos casi trucos que no están muy difundidos. Aquí vamos a ver alguno de ellos aunque no tenga que ver mucho con CSS, porque el fin de este artículo será crear una hoja de almanaque para el día actual, y otra donde se pueda seleccionar la fecha (dentro de un rango de años).

Y al avanzar con el proyecto vamos a toparnos con dificultades que debemos resolver de manera creativa.

Veamos el primer ejemplo, y luego lo estudiamos detenidamente antes de pasar a la adaptación en el segundo.

See the Pen Sheet calendar for the current day. With javascript. by solipsistaCP (@solipsistacp) on CodePen.

El HTML y el CSS no lo detallamos, solamente voy a mencionar que hay un tbody vacío porque se va a rellenar después con el escript. SCript que es el siguiente, ya comentado:

var fecha = new Date(); // CREA AL OBJETO EN VARIABLE CON FECHA DE HOY var anio = fecha.getFullYear(); // LE EXTRAE EL AÑO EN 4 DÍGITOS var mes = fecha.getMonth(); // LE EXTRAE EL MES (0 A 11 → ENERO A DICIEMBRE) var dia = fecha.getDate(); // EXTRAE EL DÍA DE LA FECHA (1 A 28; 29; 30; 31) var almanaque; function inicia() { /* NÚMERO DE MES SIGUIENTE AL ACTUAL */ var mesSig = Number(mes) + 1; /* FECHA DEL ÚLTIMO DÍA MES ACTUAL (28; 29; 30 Ó 31)*/ var diaFinal = new Date(anio, mesSig, 0).getDate(); /* DÍA SEMANA DE 1º EN MES ACTUAL */ var semana = new Date(anio, mes, 1).getDay(); /* GUARDA EN LA VARIABLE GLOBAL EL tbody A RELLENAR */ almanaque = document.getElementById("hoja"); /* INSERTA 6 FILAS VACÍAS AL tbody*/ for(tr=0; tr<6; tr++){ almanaque.insertRow(); } /* RECORRE LAS 6 FILAS CREADAS, UNA A UNA */ for(r=0; r<6; r++){ /* E INSERTA 7 CELDAS EN CADA UNA */ for(c=0; c<7; c++){ almanaque.rows[r].insertCell(); } } /* RECORRE TANTAS CELDAS COMO DÍAS TENGA EL MES Y LAS LLENA CON SU NÚMERO */ for(d=0; d<diaFinal; d++) { document.querySelectorAll("#hoja td")[d+semana].innerHTML = d+1; } /* DESTACA EL DÍA ACTUAL */ document.querySelectorAll("#hoja td")[(dia-1)+semana].className = "destaca"; } /* EJECUTA LA FUNCIÓN AL CARGAR LA PÁGINA */ onload = inicia;

Bien, empecemos con las cosas raras y particularidades de javascript. Como siempre, aclaro que hay muchas maneras de crear estos almanaques, acá sólo vemos una como excusa para estudiar sus métodos.

  • Lo primero es crear un objeto con la fecha del sistema operativo para meter en una variable. Al no usar parámetros en new Date(), captura por omisión el momento de ejecutarse con un formato del tipo Wed Oct 21 2015 16:29:00 GMT-0800, que de momento no vamos a usar, solamente necesitamos el objeto para obtener algunos valores en la siguiente línea.
  • A partir de acá, comenzamos a extraer números para usar en la creación del almanaque. Algunos pueden parecer extraños, pero los vamos explicando uno a uno. El primero es el año actual, en formato de cuatro dígitos. Después sacamos el mes, que no coincide con el formato comercial, sino que se ordenan de 0 a 11; es un detalle a tener en cuenta al hacer cálculos y conversiones. Por último (y por ahora) el día de la fecha actual.
  • Ya dentro de la función (podrían estar como globales, pero quise separar lo más estándar de las cuentas propias del proyecto) tenemos el primero de esos cálculos extraños. Necesitamos saber cuál es el mes siguiente al actual, para saber indirectamente cuál es el último día de este mes. En la variable diaFinal ya lo usamos así: creamos otro objeto Date(Año, Mes, Día) justamente para usar nuestros propios argumentos, y le decimos que devuelva sus valores a partir de una fecha en particular, que será el año actual, el mes siguiente al actual, y no el día 1º, sino el día 0, o lo que es lo mismo, el día anterior al primero. Por supuesto, será entonces el último día del mes actual, y así sabemos en qué fecha termina éste mes, sin necesidad de hacer tablas, arrays ni nada que nos complique alguna vez con los bisiestos (a puro cálculo, nada de bases de datos). Luego creamos otro objeto para usar nuevos parámetros, esta vez averiguando qué día de la semana empieza este mes.
  • Ahora sí, ubicamos el tbody por su identificador, y comenzamos a llenarlo con el almanaque propiamente dicho. Como son basicamente tablas, vamos a usar una table. Ya dejamos previsto en el código fuente las 7 celdas del encabezado con la abreviatura de los días de semana, y un caption como título, donde se pueden agregar el mes y el año. El método es simple, y se ejecuta paso por paso; como necesitamos un máximo de 6 filas por 7 columnas, primero armamos un bucle de 6 vueltas para insertar las tr. Después las recorremos una a una con otro bucle, y en cada vuelta hacemos otro más para llenar cada fila con las 7 td. La mayoría de las veces no se van a usar todas las filas, pero no molesta que estén, porque con CSS ocultamos las celdas que están vacías, y abajo apenas se ve el cellspacing entre sus filas colapsadas.
  • La parte más compleja es el llenado con los números de día. Para comenzar, el 1º del mes no tiene por qué coincidir con la primera celda de los domingos, así que aprovechamos la variable semana que contiene el día de la semana que le toca al 1º del mes en curso y que lo devuelve como un número (0 a 6 como domingo a sábado) para saltear las celdas que no se usan. La lógica es simple, si el mes empieza un miércoles, semana vale 3; y si el loop para llenar las celdas inicia por cero, con sumar 0+3 ya tenemos el índice de la primer celda a llenar, que va a coincidir con la columna de los miércoles. En la siguiente vuelta será 1+3, y se llenará la siguiente celda (del jueves), así hasta llegar a la última. Y esa última se limita con d<diaFinal en el for(). Para los números de día no hay mucho secreto, se aprovecha la misma variable incremental d, pero como empieza por cero y nosotros empezamos el mes con 1; le sumamos siempre 1.
  • Para terminar, destacamos el día actual asignando a su celda una clase que pone el texto en color blanco (y el fondo negro, si hace falta). La forma de ubicarla tiene sus vueltas. Se toma el total de celdas dentro del tablebody –para no complicarnos con las filas– y el índice de la nodeList se calcula como la variable dia menos 1 (porque los días de fecha empiezan por 1, y para esta cuenta necesitamos que calcule desde cero) y ese resultado se lo sumamos a semana (que sí cuenta desde cero) y así nos aseguramos de que el inicio de numeración "salte" las celdas en blanco del comienzo (si las hubiere). Una vez que se termina de llenar la hoja, las celdas siguientes quedan en blanco, como en cualquier almanaque clásico.

Es importante comprender la lógica de estos pasos que presentan automaticamente el día de hoy, porque el próximo es agregarle elementos de formulario para elegir día, mes y año por nosotros mismos. Así que tómense su tiempo y luego vemos la segunda versión.

Calendario con selección de fechas

Imagen ornamental

Existen versiones de almanaque que permiten elegir un día pinchando en la celda correspondiente, algo que en estos ejemplos también se puede agregar, pero no vamos a complicarlo innecesariamente. Éste tiene tres select para elegir día, mes y año respectivamente, y están ubicados dentro del caption a modo de título.

El HTML sigue con el tbody vacío, al igual que el selector de días. Resulta que los meses se pueden escribir en el código fuente, porque siempre son los mismos doce, el año se resolvió fácil ofreciendo sólo diez opciones para los teenyears de este siglo (por no usar un input con datalist), pero los días se rellenan dependiendo del mes elegido, así que también hay que usar el escript. Que, por supuesto, aprovecha el mismo loop que puebla la hoja del almanaque.

Hay también unos eventos JS en los selectores que son autoexplicativos, pero lo mismo vamos a detallarlos más abajo.

El script comentado es igual al anterior, y agregamos unas líneas para el formulario.

See the Pen Sheet calendar with ´select´ 's for date. With javascript. by solipsistaCP (@solipsistacp) on CodePen.

var fecha = new Date(); // CREA AL OBJETO EN VARIABLE CON FECHA DE HOY var anio = fecha.getFullYear(); // LE EXTRAE EL AÑO EN 4 DÍGITOS var mes = fecha.getMonth(); // LE EXTRAE EL MES (0 A 11 → ENERO A DICIEMBRE) var dia = fecha.getDate(); // EXTRAE EL DÍA DE LA FECHA (1 A 28; 29; 30; 31) var almanaque, selectorDias; function inicia() { /* NÚMERO DE MES SIGUIENTE AL ACTUAL */ var mesSig = Number(mes) + 1; /* FECHA DEL ÚLTIMO DÍA MES ACTUAL (28; 29; 30 Ó 31)*/ var diaFinal = new Date(anio, mesSig, 0).getDate(); /* DÍA SEMANA DE 1º EN MES ACTUAL */ var semana = new Date(anio, mes, 1).getDay(); /* GUARDA EN VARIABLE GLOBAL EL tbody, A RELLENAR */ almanaque = document.getElementById("hoja"); /* GUARDA EN VARIABLE GLOBAL EL select PARA LOS DÍAS, A RELLENAR */ selectorDias = document.getElementById("losDias"); /* VACÍA EL tbody SI TIENE CONTENIDO */ almanaque.innerHTML = ""; /* VACÍA EL select PARA LOS DÍAS SI TIENE CONTENIDO */ selectorDias.innerHTML = ""; /* INSERTA 6 FILAS VACÍAS AL tbody*/ for(tr=0; tr<6; tr++){ almanaque.insertRow(); } /* RECORRE LAS 6 FILAS CREADAS, UNA A UNA */ for(r=0; r<6; r++){ /* E INSERTA 7 CELDAS EN CADA UNA */ for(c=0; c<7; c++){ almanaque.rows[r].insertCell(); } } /* LOOP CON LA MISMA CANTIDAD DE DÍAS DEL MES */ for(d=0; d<diaFinal; d++) { /* CREA TANTAS OPCIONES COMO DÍAS TENGA EL MES Y LAS LLENA CON SU NÚMERO */ selectorDias.options[d] = new Option(d+1); /* RECORRE TANTAS CELDAS COMO DÍAS TENGA EL MES Y LAS LLENA CON SU NÚMERO */ document.querySelectorAll("#hoja td")[d+semana].innerHTML = d+1; } /* MUESTRA AL SELECTOR DE DÍAS EN EL VALOR QUE COINCIDA CON dia */ document.getElementById("losDias").value = dia; /* MUESTRA AL SELECTOR DE MESES EN EL VALOR QUE COINCIDA CON mes */ document.getElementById("losMeses").value = mes; /* MUESTRA AL SELECTOR DE AÑOS EN EL VALOR QUE COINCIDA CON anio */ document.getElementById("losAnios").value = anio; /* DESTACA EL DÍA ACTUAL EN LA HOJA DE ALMANAQUE */ document.querySelectorAll("#hoja td")[(dia-1)+semana].className = "destaca"; } /* EJECUTA LA FUNCIÓN AL CARGAR LA PÁGINA */ onload = inicia;

Vamos a ocuparnos principalmente del código nuevo.

  • Agregamos una variable para manejar el selector de días, que originalmente está vacío. Como ahora existe la posibilidad de cambiar las fechas y de que haya que reescribir el contenido del formulario, a hoja y losDias hay que vaciarlos antes de darles un nuevo relleno.
  • Luego de creadas las filas y celdas, se procede a llenar cada td con el número de día que le corresponde según el mes y el año, y se aprovecha el mismo bucle for(d=0; d<diaFinal; d++) para crear igual cantidad de nuevas opciones en el selector de días, por supuesto, con el mismo número como text. Aunque no lo veamos en el código fuente, queda algo así:
    <option>1</option>
    · · ·
    <option>31</option>

    Al no contener un atributo value="", el navegador interpreta que el valor es justamente su texto visible. Para el caso de la colección de options, cada índice o ítem es simplemente la misma variable incremental d del loop, porque ambas comienzan de cero; y en el text se le suma 1 porque los días del mes empiezan por 1.
  • Al cargarse el documento por primera vez los valores de fecha se capturan del reloj del sistema operativo, por lo que debemos hacerlos coincidir con los selectores que ofician de "título". El método más sencillo es "asignarles" esos mismos valores, porque automaticamente se van a mostrar en esa opción tal y como si tuviese el atributo selected. El modo de destacar el día dentro de la hoja es igual que el anterior.

Las líneas de javascript que están en los selectores, como por ejemplo onchange="dia=this.value; inicia()"> ponen como valor de cada variable el mismo que tenga elegido su select, reemplazando al anterior cada vez que se cambie la opción; inmediatamente después disparan la función inicio(), donde serán esos y ya no los de new Date() (u otros elegidos previamente) los que se usen para mostrar una hoja de almanaque distinta a la del día de hoy.

Mejoras posibles al calendario

Como se imaginarán, este invento sirve también para pasar fechas a otro formulario o a otra página; y también al servidor para ser procesadas por lenguajes como PHP. Si lo prueban en un documento local, pueden usar el boton submit para ver en la barra de direcciones cómo se envían el año, el mes y el día elegidos.

Estuve muy tentado para agregar esa versión tipo "datepicker", pero una vez que tenemos el mes y el año actuales en variables, aumentar o disminuir sus valores de a "1" con botones "◄" y "►" es demasiado sencillo. Agregando inicio() al final del mismo onclick="" funcionarían igual que los select previos.

Lo de pinchar en las celdas para cambiar la variable dia sería apenas más complicado. Se puede hacer un addEventListener( "click", function(event){} ) recorriendo todas las celdas llenas o buscando el elemento por sus coordenadas, pero si ya tenemos un bucle que escribe el contenido una a una, lo podemos aprovechar para agregarles el atributo onclick que ejecute alguna función para capturar su texto (así obtenemos el número de día) y la pase a la variable correspondiente, y luego sí que ejecute inicio().

De la misma forma se pasan luego valores a otro formulario, otra página madre, algún programa en el servidor; en fin, se crea el JS según las necesidades.

Si alguien tiene ganas de escribir ese almanaque, éste es un buen lugar para compartirlo. Pero les advierto: hacer que los distintos métodos para ingresar la fecha y las cajas para mostrar la confirmación se ajusten entre ellos, ya es algo más difícil.

Porque siempre los últimos detalles suelen ser los más difíciles. :-P

Autoría

Furoya: Autor del artículo

Artículo original de Furoya.
La intención del autor con sus colaboraciones no es que los artículos sirvan para hacer un copy&paste de códigos sino que comprendas y aprendas la lógica y el cómo trabaja javaScript.
Y a partir de lo expuesto experimentes tú.
El autor del post y el editor del blog te animamos a que plantees tus dudas o reflexiones y que compartas tus realizaciones en base a lo expuesto en los comentarios. Recuerda que puedes incluir pens (ejemplos en Codepen.io) en ellos.