Mostrar un navegador por meses en el calendario

  • Por
El calendario jQuery necesita una pequeña utilidad para moverse entre distintos meses y poder ver cualquier mes de cualquier año.
En este artículo vamos a ponerle unos controles para actualizar el mes que se está mostrando en el calendario. Hasta el capítulo anterior del Manual del Calendario jQuery habíamos creado un calendario que mostraba siempre el mes y año actual, según la fecha configurada en el sistema operativo. Así que vamos a trabajar un poco para poder navegar al mes siguiente y mes anterior.

De paso, veremos cómo agregar un control para cerrar la ventana del calendario con un pequeño efectillo que jQuery hará por nosotros.

A lo largo de este artículo conseguiremos llegar a este estado del desarrollo del calendario.

Layout del calendario actualizado

Igual que hicimos para empezar a crear el calendario, hemos realizado primero la maquetación de los controles que vamos a incorporar en este artículo.

El nuevo layout del calendario incluye el nombre de mes, un botón para ir al mes siguiente y anterior y el botón para ocultar el componente.

Contol para cerrar el calendario

De entre todos los nuevos controles que estamos colocando a este calendario, el más básico es el que sirve para cerrar la capa para la selección de fechas. Es un simple enlace-botón que tiene un evento clic asociado que oculta la capa del calendario.

//control para ocultar el calendario
var botonCerrar = $('<a href="#" class="calencerrar"><span></span></a>');
botonCerrar.click(function(e){
   e.preventDefault();
   calendario.hide("slow");
})
var capaCerrar = $('<div class="capacerrar"></div>');
capaCerrar.append(botonCerrar)

Controles para mes siguiente y anterior

Hemos definido un par de controles para navegar a los meses siguiente y anterior. Son parecidos al control de cerrar, con la particularidad que en el evento clic actualizan las variables locales al plugin mes y año, para trasladarse al siguiente o al anterior. Luego llama a una función que veremos dentro de poco, llamada muestraDiasMes(), para actualizar las capas con los días del mes.

//controles para ir al mes siguiente / anterior
var botonMesSiguiente = $('<a href="#" class="botonmessiguiente"><span></span></a>');
botonMesSiguiente.click(function(e){
   e.preventDefault();
   mes = (mes + 1) % 12;
   if (mes==0)
      ano++;
   capaDiasMes.empty();
   muestraDiasMes(mes, ano);
})
var botonMesAnterior = $('<a href="#" class="botonmesanterior"><span></span></a>');
botonMesAnterior.click(function(e){
   e.preventDefault();
   mes = (mes - 1);
   if (mes==-1){
      ano--;
      mes = 11
   }   
   capaDiasMes.empty();
   muestraDiasMes(mes, ano);
})

Esos dos botones, igual que el botón para cerrar la capa, deben añadirse a la capa del calendario, con funciones append() que veremos en el código completo del plugin.

Función para mostrar los días

Hasta ahora el calendario sólo mostraba el mes y año actuales y no se podía cambiar a no ser que se modificase la hora del sistema operativo. En este paso tenemos que mostrar los días de cualquier mes y año en el calendario, ya que vamos a tener que navegar entre distintos meses y años para seleccionar una fecha.

Por ello hemos separado el código que sirve para la generación de los días a una función aparte. Esa función recibirá los parámetros de mes y año del calendario a mostrar y calculará los días disponibles en ese mes y mostrará las capas correspondientes.

Esta función se llamará muestraDiasMes() y la veremos a continuación en el código completo del plugin.

Código completo del plugin

El código de nuestro plugin para la selección de fechas se puede ver a continuación. Aun le faltan varias cosas, pero ya se ve mucho más dinámico con las opciones para navegar por los meses.

jQuery.fn.calendarioDW = function() {
this.each(function(){
      //saber si estoy mostrando el calendario
      var mostrando = false;
      //variable con el calendario
      var calendario;
      //variable con los días del mes
      var capaDiasMes;
      //variable para mostrar el mes y ano que se está viendo
      var capaTextoMesAnoActual = $('<div class="mesyano"></div>');
      //iniciales de los días de la semana
      var dias = ["l", "m", "x", "j", "v", "s", "d"];
      //nombres de los meses
      var nombresMes = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]
      
      //elemento input
      var elem = $(this);
      //creo un enlace-botón para activar el calendario
      var boton = $("<a class='botoncal' href='#'><span></span></a>");
      //inserto el enlace-botón después del campo input
      elem.after(boton);
      
      //evento para clic en el botón
      boton.click(function(e){
         e.preventDefault();
         mostrarCalendario();
      });
      //evento para clic en el campo
      elem.click(function(e){
         this.blur();
         mostrarCalendario();
      });
      
      //función para mostrar el calendario
      function mostrarCalendario(){
         if(!mostrando){
            mostrando = true;
            //es que hay que mostrar el calendario
            //dias de la semana
            var capaDiasSemana = $('<div class="diassemana"></div>');
            $(dias).each(function(indice, valor){
               var codigoInsertar = '<span';
               if (indice==0){
                  codigoInsertar += ' class="primero"';
               }
               if (indice==6){
                  codigoInsertar += ' class="domingo ultimo"';
               }
               codigoInsertar += ">" + valor + '</span>';
               
               capaDiasSemana.append(codigoInsertar);
            });
            
            //capa con los días del mes
            capaDiasMes = $('<div class="diasmes"></div>');
            //un objeto de la clase date para calculo de fechas
            var objFecha = new Date()
            //mes y año actuales
            var mes = objFecha.getMonth();
            var ano = objFecha.getFullYear();
            //muestro los días del mes y año dados
            muestraDiasMes(mes, ano);
            
            //control para ocultar el calendario
            var botonCerrar = $('<a href="#" class="calencerrar"><span></span></a>');
            botonCerrar.click(function(e){
               e.preventDefault();
               calendario.hide("slow");
            })
            var capaCerrar = $('<div class="capacerrar"></div>');
            capaCerrar.append(botonCerrar)
            
            //controles para ir al mes siguiente / anterior
            var botonMesSiguiente = $('<a href="#" class="botonmessiguiente"><span></span></a>');
            botonMesSiguiente.click(function(e){
               e.preventDefault();
               mes = (mes + 1) % 12;
               if (mes==0)
                  ano++;
               capaDiasMes.empty();
               muestraDiasMes(mes, ano);
            })
            var botonMesAnterior = $('<a href="#" class="botonmesanterior"><span></span></a>');
            botonMesAnterior.click(function(e){
               e.preventDefault();
               mes = (mes - 1);
               if (mes==-1){
                  ano--;
                  mes = 11
               }   
               capaDiasMes.empty();
               muestraDiasMes(mes, ano);
            })
            //capa para mostrar el texto del mes y ano actual
            var capaTextoMesAno = $('<div class="textomesano"></div>');
            capaTextoMesAno.append(botonMesSiguiente);
            capaTextoMesAno.append(botonMesAnterior);
            capaTextoMesAno.append(capaTextoMesAnoActual);
            
            //calendario y el borde
            calendario = $('<div class="capacalendario"></div>');
            var calendarioBorde = $('<div class="capacalendarioborde"></div>');
            calendario.append(calendarioBorde);
            calendarioBorde.append(capaCerrar);
            calendarioBorde.append(capaTextoMesAno);
            calendarioBorde.append(capaDiasSemana);
            calendarioBorde.append(capaDiasMes);
            
            //inserto el calendario en el documento
            $(document.body).append(calendario);
            //lo posiciono con respecto al boton
            calendario.css({
               top: boton.offset().top + "px",
               left: (boton.offset().left + 20) + "px"
            })
            //muestro el calendario
            calendario.show("slow");
            
         }else{
            //es que el calendario ya se estaba mostrando...
            calendario.fadeOut("fast");
            calendario.fadeIn("fast");
            
         }
         
      }
      
      function muestraDiasMes(mes, ano){
         //muestro en la capaTextoMesAno el mes y ano que voy a dibujar
         capaTextoMesAnoActual.text(nombresMes[mes] + " " + ano);
         
         //muestro los días del mes
         var contadorDias = 1;
         
         //calculo la fecha del primer día de este mes
         var primerDia = calculaNumeroDiaSemana(1, mes, ano);
         //calculo el último día del mes
         var ultimoDiaMes = ultimoDia(mes,ano);
         
         //escribo la primera fila de la semana
         for (var i=0; i<7; i++){
            if (i < primerDia){
               //si el dia de la semana i es menor que el numero del primer dia de la semana no pongo nada en la celda
               var codigoDia = '<span class="diainvalido';
               if (i == 0)
                  codigoDia += " primero";
               codigoDia += '"></span>';
            } else {
               var codigoDia = '<span';
               if (i == 0)
                  codigoDia += ' class="primero"';
               if (i == 6)
                  codigoDia += ' class="ultimo domingo"';
               codigoDia += '>' + contadorDias + '</span>';
               contadorDias++;
            }
            var diaActual = $(codigoDia);
            capaDiasMes.append(diaActual);
         }
         
         //recorro todos los demás días hasta el final del mes
         var diaActualSemana = 1;
         while (contadorDias <= ultimoDiaMes){
            var codigoDia = '<span';
            //si estamos a principio de la semana escribo la clase primero
            if (diaActualSemana % 7 == 1)
               codigoDia += ' class="primero"';
            //si estamos al final de la semana es domingo y ultimo dia
            if (diaActualSemana % 7 == 0)
               codigoDia += ' class="domingo ultimo"';
            codigoDia += '>' + contadorDias + '</span>';
            contadorDias++;
            diaActualSemana++;
            var diaActual = $(codigoDia);
            capaDiasMes.append(diaActual);
         }
         
         //compruebo que celdas me faltan por escribir vacias de la última semana del mes
         diaActualSemana--;
         if (diaActualSemana%7!=0){
            //console.log("dia actual semana ", diaActualSemana, " -- %7=", diaActualSemana%7)
            for (var i=(diaActualSemana%7)+1; i<=7; i++){
               var codigoDia = '<span class="diainvalido';
               if (i==7)
                  codigoDia += ' ultimo'
               codigoDia += '"></span>';
               var diaActual = $(codigoDia);
               capaDiasMes.append(diaActual);
            }
         }
      }
      //función para calcular el número de un día de la semana
      function calculaNumeroDiaSemana(dia,mes,ano){
         var objFecha = new Date(ano, mes, dia);
         var numDia = objFecha.getDay();
         if (numDia == 0)
            numDia = 6;
         else
            numDia--;
         return numDia;
      }
      
      //función para ver si una fecha es correcta
      function checkdate ( m, d, y ) {
         // función por http://kevin.vanzonneveld.net
         // extraida de las librerías phpjs.org manual en http://www.desarrolloweb.com/manuales/manual-librerias-phpjs.html
         return m > 0 && m < 13 && y > 0 && y < 32768 && d > 0 && d <= (new Date(y, m, 0)).getDate();
      }
      
      //funcion que devuelve el último día de un mes y año dados
      function ultimoDia(mes,ano){
         var ultimo_dia=28;
         while (checkdate(mes+1,ultimo_dia + 1,ano)){
          ultimo_dia++;
         }
         return ultimo_dia;
      }
      
});
return this;
};

Podemos ver el ejemplo en marcha, tal como ha quedado con estas modificaciones.

En el siguiente artículo nos aproximaremos bastante a la finalización del calendario, haciendo que los números de los días sean seleccionables, con lo que nuestro componente ya funcionará como lo que se conoce como datepicker.