January 15th, 2009

Javascript Calendar and Thoughts on Jquery

I am one of those rare developers who really doesn't mind having to work with Javascript. In fact, with all the cool libraries available to developers nowadays, cross-browser issues that previously made working with Javascript a chore have all but been eliminated. My favourite Javascript library happens to be Jquery. I find Jquery's syntax and methods easy to work with and its structure to be logical and well thought out - not to mention Jquery's large community support base.


Anyway, I had a client who was thinking of revamping their online registration system and they wanted some of those fancy little popup calendars for date picking. Jquery has a great plug-in called 'Datepicker' which is quite handy for this sort of thing but I decide to create my own just for fun. Sometimes I code just for the sake of coding - I mean, it's fun to know how things work right?


If you would like to download the example, you can do so here or, to see the calendar in operation, click on the image below:



The part that I was most curious about was how I was going to account for the leap years algorithmically (And no, it's not as easy as checking for divisibility by four - to understand why, check this out).


Anyway, Google brought me to the Wikipedia entry for 'Leap year' where I found this bit of 'psuedocode':



function isLeapYear (year):
	if ((year modulo 4 is 0) and (year modulo 100 is not 0)) or (year modulo 400 is 0)
		then true
	else false

Which, in Javascript, reads like this (where 'dateArray[1]' is the month of Febuary):



if (((year % 4 == 0) & (year % 100 != 0)) || (year % 400 == 0)) {
		dateArray[1] = 28;
	} else {
		dateArray[1] = 29;
	}

And here's the main script:



// Arrays
var monthArray = ['January','Febuary','March','April','May','June','July','August','September','October','November','December'];
var dateArray = [31,29,31,30,31,30,31,31,30,31,30,31];
var dayArray = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

// Current date
var d = new Date();
var day = d.getDate();
var month = d.getMonth();
var year = d.getFullYear();

// Utility - used to calculate day of week first day of month falls upon
var dx = new Date();

// Toggle vars
var calendar;
var style;

// Onload handler - function executes once page has been loaded
function onloadHandler() {
	getCurrentDate();
	
	// Get style
	calendar = document.getElementById("calendar");
	if (calendar.currentStyle) {
		style = calendar.currentStyle["visibility"];
	} else if (window.getComputedStyle) {
		style = document.defaultView.getComputedStyle(calendar,null).getPropertyValue("visibility");
	}
}

// Toggle visibility - make calendar visible when input is 'clicked'
function toggleVisibility() {
	calendar.style.visibility = 'visible';
}

// Get current date - change input value to current date on page load
function getCurrentDate() {
	var output = document.getElementById("formatted-date");
	output.value = monthArray[month] + ' ' + day + ', ' + year;
}

// Get date - get the choosen date when calendar day 'clicked' on
function getDate(dayOfMonth) {
	var output = document.getElementById("formatted-date");
	output.value = monthArray[month] + ' ' + dayOfMonth.innerHTML + ', ' + year;
	calendar.style.visibility = 'hidden';
}	
	
// Change month - decrement/increment month
function changeMonth(movement) {
	var theMonth = document.getElementById("theMonth");
	var theYear = document.getElementById("theYear");
	
	// Set back a month
	if (movement == 'back') {
		if (month == 0) {
			var back = month = 11;
			year -= 1;
			theYear.innerHTML = year;
		} else {
			var back = month -= 1;
		}
	}
	
	// Set forward a month
	if (movement == 'forward') {
		if (month == 11) {
			month = 0;
			var forward = monthArray[month];
			year += 1;
			theYear.innerHTML = year;
		} else {
			month += 1;
			var forward = monthArray[month];
		}
	}
	
	// Change month
	theMonth.innerHTML = month;
	addCalendar()
}		

// Add calendar
function addCalendar() {

	// Establish day of week that first day falls upon
	dx.setFullYear(year,month,1);
	var xCount = dx.getDay();
	
	// Account for leap years
	if (((year % 4 == 0) & (year % 100 != 0)) || (year % 400 == 0)) {
		dateArray[1] = 28;
	} else {
		dateArray[1] = 29;
	}
	
	// Get element for insertion
	var theTest = document.getElementById("calendar");
	
	// Add table head
	var data = '<table><tr><td class="arrow" onclick="changeMonth('back')"></td><td id="theMonth" class="month-year" colspan="3">'; 
	data += monthArray[month] + '</td><td id="theYear" class="month-year" colspan="2">';
	data += year + '</td><td class="arrow" onclick="changeMonth('forward')"></td></tr>';
	
	// Add days of the week
		data += '<tr>';
	for (var i in dayArray) {
		data += '<td>' + dayArray[i] + '</td>';
	} 
	data += '</tr><tr>';
	
	// Position first day of month
	if (dx.getDay() != 0) {
		data += '<td id="spacer" colspan="' + dx.getDay() + '"></td>';
	} 
	
	// Add calendar days	
	for (var i = 1; i <= dateArray[month]; i++) {
	xCount += 1;
		if (xCount % 7 == 1 && xCount != 1) {
			data += '</tr><tr>';
		}
		data += '<td onclick="getDate(this)">' + i + '</td>';
	}
	data += '</tr></table>';
	
	// Add data to element
	theTest.innerHTML = data;
}

Make sure to download the ZIP if you really want to get a good look at how things work. I should point out that this was coded in the old 'obtrusive' way (blush). This is not the way I would code for production and would suggest that, if you plan on using this script, event handlers be registered programatically rather than inline - in a more 'unobtrusive' manor. Better yet, why not let Jquery do most of the heavy lifting. Jquery, by design, makes for better scripting because it forces you to code in an unobtrusive manor. So, while this script may be instructive, it would be best to do as I say, not as I do.


Enjoy!

Bookmark and Share

1June 9th, 2009 at 2:44 am

Javascript Geek writes:

Nice code example, especially the leap year issues - great find!