Generate a full calendar with one jQuery based function

See the Pen Jquery Calendar by Pato Salazar (@PatoSalazarC) on CodePen.10834

DOWNLOAD PROJECT FROM GITHUB

Back in 2014, when I was taking the WDDM program at Humber College, one of my teachers, Thomas Borzecki (@IBorg) showed us how to generate a full calendar using PHP, thanks to a clever usage of the PHP Date Object.

Although the implementation was not difficult, the logic behind the code provides a great example in how to solve a complex programming challenge by using a gradual approach.

So I decided to adapt Thomas’ code into jQuery and write a 7-step tutorial on how to generate a calendar with one Jquery function.

HTML and CSS

<div id="wrapper">
    <h1><span id="prev" class="changeMonth"> < << </span>  <span id="monthAndYear"> Month and Year </span>  <span id="next" class="changeMonth"> >>> </span></span></h1>
    <table>
      <tr id="daysNames">
        <td>Sun</td>
        <td>Mon</td>
        <td>Tue</td>
        <td>Wed</td>
        <td>Thu</td>
        <td>Fri</td>
        <td>Sat</td>
      </tr>
    </table>
</div>

As you might have thought, we need use a table with a single tr tag for the days of the week. The rest of the mark-up will be generated by jQuery. As for the CSS, it’s mostly minor style choices to make the calendar a bit more pleasant to the eye, but adds nothing critical to the dynamic generation of the calendar, with the exception of the class that is going to style the current day.

@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700);

body{
	font-family: 'Open Sans', sans-serif;
	font-size: 16px;
	background: #f3f3f3;
}

#wrapper{
	width: 70%;
	margin: auto;
}

h1{
	text-align: center;
}

.changeMonth{
	cursor: pointer;
}

table {
	width:100%;
	background: white;
}

#daysNames{
	font-weight: bold;
	color:white;
	background: black;
}

#daysNames td{
	font-size: 120%;
}

td{
	width: 10%;
	text-align: center;
	font-size: 200%;
}

.current_day {
	background: black;
	color: white;
}

JQUERY
Here is the fun part. Let’s get to it
Step 1:
Useful Variables.

Our first step is to retrieve the necessary information we need to generate the current month:

$(document).ready(function(){
	// Date References
	var dateReference = new Date();
	var calendarYear = dateReference.getFullYear(); 
	var currentMonthIndex = dateReference.getMonth();
	var daysPerWeek = 7;
	
	// array of month names to be displayed
	var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

	// Jquery Object
	var $monthTable = $("table");
}); // end document.ready
  1. dateReference: we create an instance of the JavaScript Date() object.
  2. calendarYear: this retrieves the current year.
  3. currentMonthIndex: The Date() object give us an index per each month of the year (0 – 11). This is how we get the current one.
  4. daysPerWeek: a constant that indicates the number of days on a week.
  5. monthArray: this array contains abbreviations for each month name.
  6. $monthTable: this variable holds our table and will allow us to add and delete html elements at will from it

These variables are the foundation stone for the method that will automatically generate the months in our calendar.

Step 2
Create Method and Set Local Scope Variables

With the general variables created, we can start to extrapolate information inside our generateMonth() method:

function generateMonth(){
   // get start date and day of month
   var firstDayOfTheMonth = new Date(calendarYear, currentMonthIndex, 1);

   // get last day and date of the month
   var lastDayOfTheMonth = new Date(calendarYear, currentMonthIndex + 1, 0);

   // calculate how many weeks the month needs to have.
   var weeksPerMonth = Math.ceil(lastDayOfTheMonth.getDate() / daysPerWeek)

   // calculate how many cell the grid needs to have for the month
   var totalCells = weeksPerMonth * daysPerWeek;

   // generate information to track loop
   var currentCell = 0;
    var monthDays = 0;
}
  1. firstDayOfTheMonth = This variable give us access to the Date() object for first day of the current month.
  2. lastDayOfTheMonth: This variable give us access to the Date() object for last day of the current month.
  3. weeksPerMonth = this variable will calculate how many rows our table needs to have to hold all the days in the month. As you can read we are dividing the total number of days in the month, by the length of a week. So if the month has 30 days, you’ll need 4.2 rows to be able to put every day in the table. However you can’t have 4.2 weeks in a calendar. So we have to round the number up, so we can have an integer as a result (in this case 5).
  4. totalCells = So knowing the amount of rows needed for our table we can know the amount of individual cells the table is going to have by multiplying weeksPerMonth by daysPerWeek. Remember that in week 1 and the last one, some of the cells might be used by days in the previous and next month respectively, but now we know how much space our current month needs to get entirely displayed, without worrying when in the week it starts.
  5. currentCell and monthDays = this are flags that will allows to keep track of the loops we need to create to generate the month.
  6. Step 3
    Let’s add the month name and empty weeks in the table

    Now we have all the information we need to generate our current month.

    Using the monthArray, currentMonthIndex array and calendarYear variables, we can create a string with the current month and current year and use it as the title of our calendar.

    // add month and year to page h1 tag
    $("#monthAndYear").html(monthNames[currentMonthIndex] + " " + calendarYear);
    

    Next we run a for loop using the weekperMonth variable to append the necessary rows into our table. Note that we are adding a class (.weeks) to each row that will be useful later.

     // add the necessary tr tags to represent each week of the month
    for (var i = 0; i < weeksPerMonth; i++) {
      $monthTable.append("<tr class='weeks'>");
    }; 
    

    Step 4
    Add the days per each week

    After our code appends the necessary weeks to the calendar, we need to target each week and add seven days.

    To do this, we need to target the class “.weeks” to be able to generate append the td tags for each day of the week. Here we use the currentCell variable to track which cell we are adding currently. Every time the loop runs, we add one to currentCell.

    $(".weeks").each(function() {
    	for (var i = 0; i < daysPerWeek; i++) {
    	  $(this).append("<td class='not-this-cell'>&nbsp;");
            }
        }
    )
    

    Step 5
    Check When the Month Starts and Ends

    If you are error free at this point, you should be able to see the every day of every week with its corresponding number. This does not make sense as you probably have more days than the month has, and it is probably starting on the wrong day.

    So we need a way to check when do we need to start and stop adding numbers in our weeks. This can be achieved with the following piece of code:

    
    $(".weeks").each(function() {
         for (var i = 0; i < daysPerWeek; i++) {
              if (currentCell < firstDayOfTheMonth.getDay() || (monthDays >= lastDayOfTheMonth.getDate())) {
    	     $(this).append("<td class='not-this-cell'>&nbsp;</td>");
               }
               else {
    	      // if day is assigned to the day, add 1 to monthDays tracker 
    	      // and added to the cell
    
    	      monthDays++;
                  $(this).append("<td>" + monthDays + "</td>");
                }
         }
    
          // move over the next cell
          currentCell++;
    )}
    
    

    What are we doing here is to use our firstDayOfTheMonth variable to get the day index of the first day of the month (from 0 [Sunday] to 6 [Saturday]).

    So if our currentCell flag is less than the index of the first day of the month, we add empty

    tags with a space entity as we can safely assume those day don’t belong to the current month.

    Using the same logic we can take advantage of the lastDayOfTheMonth variable to get the actual last day. So if monthDays is more or equal than lastDayOfTheMonth, we also add empty td tags.

    In any other case, we just append the monthDays value in our dynamically generated tags and we increment our tracking flags by one (monthDays and currentCell)

    Step 6
    Detect the current Day

    With our current month displaying correctly, we need to figure out a way to detect what is the current day.

    To do this we need to use the dateReference.getMonth() and check if is equal to our currentMonthIndex variable. If this is true and also the dateReference.getDate() is equal to our monthDays variable, it means that the loop is actually appending the actual current day of the year. So here we just add a tr tag with the .currentDay css class to highlight it.

    
    $(".weeks").each(function() {
         for (var i = 0; i < daysPerWeek; i++) {
              if (currentCell < firstDayOfTheMonth.getDay() || (monthDays >= lastDayOfTheMonth.getDate())) {
    	     $(this).append("<td class='not-this-cell'>&nbsp;</td>");
               }
               else {
    	      // if day is assigned to the day, add 1 to monthDays tracker 
    	      // and added to the cell
    
    	      monthDays++;
    
                 // detect current day
    	     if(dateReference.getMonth() === currentMonthIndex && dateReference.getDate() === monthDays) {
    		   $(this).append("<td class='current_day'>" + monthDays + "</td>");
    	      }
    	      else {
    		   $(this).append("<td>" + monthDays + "</td>");
    	      }	
              }
         }
    
          // move over the next cell
          currentCell++;
    )}
    
    

    With all this setup ready, the only thing that is left is to call our generateMonth() method and voila!

    Step 7
    Generate Previous and Next Month

    Thanks to the method we created, we can have our current month when the site loads, but if we connect a couple of click events we can iterate through all the months of the year.

    //change month --
    $("#prev").click(function() {
    
    	// remove all generated weeks on load
    	$(".weeks").remove();
    
    	// decrease index
    	currentMonthIndex--;
    
    	// detect if we are in Jan
    	if(currentMonthIndex === -1)
    	{	
    	     // reset index to DEC
    	     currentMonthIndex = 11;
    	}
    
    	// generate the previous month
    		generateMonth();
       }
    );
    
    //change month ++
    $("#next").click(function() {
    
    	// remove all generated weeks on load
    	$(".weeks").remove();
    
    	//increase index
    	currentMonthIndex++;
    
    	// detect if we are in Dec
    	if(currentMonthIndex === 12)
    	{	
                 // reset index to JAN
    	     currentMonthIndex = 0;
    	}
    
    	// generate next month
    	generateMonth();
    
         }
    );
    

    The logic behind this last part is very simple. As soon as we run any of our “click” function, we remove all weeks from the table, we increase / decrease the currentMonthIndex and just call our generateMonth() method again.

    The if / else statement is to check if we need to jump from December to January or vice versa (depending if we are going up or down through the year).

    Hope you had enjoy this tutorial and here is the full JS Code.

    $(document).ready(function(){
    	// Date References
    	var dateReference = new Date();
    	var calendarYear = dateReference.getFullYear(); 
    	var currentMonthIndex = dateReference.getMonth();
    	var daysPerWeek = 7;
    	
    	// array of month names to be displayed
    	var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
      						"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    
    	// Jquery Object
    	var $monthTable = $("table");
    
    	function generateMonth(){
    		// get start date and day of month
    		var firstDayOfTheMonth = new Date(calendarYear, currentMonthIndex, 1);
    
    		// get last day and date of the month
    		var lastDayOfTheMonth = new Date(calendarYear, currentMonthIndex + 1, 0);
    
    		// calculate how many weeks the month needs to have.
    		var weeksPerMonth = Math.ceil(lastDayOfTheMonth.getDate() / daysPerWeek)
    
    		// calculate how many cell the grid needs to have for the month
    		var totalCells = weeksPerMonth * daysPerWeek;
    
    		// generate information to track loop
    		var currentCell = 0;
    		var monthDays = 0;
    
    		// add month and year to page h1 tag
      		$("#monthAndYear").html(monthNames[currentMonthIndex] + " " + calendarYear);
    
      		// add the necessary tr tags to represent each week of the month
    		for (var i = 0; i < weeksPerMonth; i++) {
    			$monthTable.append("<tr class='weeks'>");
    		}; 
    
    		// add days of the weeks per month
    		$(".weeks").each(function() {
    			for (var i = 0; i < daysPerWeek; i++) {
    				// if cells have no day assign to them add td with a space
    				// and disable hover effect
    				if (currentCell < firstDayOfTheMonth.getDay() || (monthDays >= lastDayOfTheMonth.getDate())) {
    					$(this).append("<td class='not-this-cell'>&nbsp;</td>");
    				} 
    				else {
    					// if day is assigned to the day, add 1 to monthDays tracker 
    					// and added to the cell
    					monthDays++;
    
    					// detect current day
    					if(dateReference.getMonth() === currentMonthIndex && dateReference.getDate() === monthDays) {
    						$(this).append("<td class='current_day'>" + monthDays + "</td>");
    					}
    					else {
    						$(this).append("<td>" + monthDays + "</td>");
    					}				
    				};
    
    				// move over the next cell
    				currentCell++;
    			};
    		});
    	}
    
    	// call generateFunction
    	generateMonth()
    
    	//change month --
    	$("#prev").click(function() {
    
    		// remove all generated weeks on load
    		$(".weeks").remove();
    
    		// decrease index
    		currentMonthIndex--;
    
    		// detect if we are in Jan
    		if(currentMonthIndex === -1)
    		{	
    			// reset index to DEC
    			currentMonthIndex = 11;
    		}
    
    		// generate the previous month
    		generateMonth();
    	});
    
    	//change month ++
    	$("#next").click(function() {
    
    		// remove all generated weeks on load
    		$(".weeks").remove();
    
    		//increase index
    		currentMonthIndex++;
    
    		// detect if we are in Dec
    		if(currentMonthIndex === 12)
    		{	
    			// reset index to JAN
    			currentMonthIndex = 0;
    		}
    
    		// generate next month
    		generateMonth();
    
    	});
    
    })// end document.ready
    
Share on Facebook2Tweet about this on TwitterShare on LinkedIn1

Leave a Comment

Your email address will not be published. Required fields are marked *