CURRENT PROJECTS
jCal
jCal - Animated multi-month datepicker jQuery plugin with multi-day selection.
jCal
jTPS - Clean data table jQuery plugin with natural sort and animated pagination.
jCal
jVal - jQuery Form field validation plugin with appealing error alerts that integrate into form layout.
jCal
jsyn - Super-fast and lightweight Javascript Syntax Highlighter.
jCal
Mammoth Mountain Flash Terrain Map - Mammoth Mountain Ski Area in simple and barely interactive Papervision3D demonstration.
CATEGORIES AND POSTS
overset
DEVELOPMENT LOG FOR JIM PALMER
Posted 07/10/2007 in dhtml


I've seen my share of Dynamic HTML Date Pickers, Javascript Calendar controls, and the like. Each of which have a more horrifically ugly interface and are bloated with features. I found a need to create a simple calender piece so that a user could add a product to their cart when clicking on a specific date.

The example below shows two instantiated calendar's each automatically showing the current month as well as blocking out the days before the current day in the previous month and blocking out days after the current day in the next month.

Update 10/31/2007 - v0.4 Update 9/24/2007 - v0.3 Update 9/21/2007 - v0.2 The javascript Calendar "class" I came up with offer these features: This was minimally tested on IE 6+, FF, and Safari 2.X.

The source for instantiating the test cases of the Calendar Javascript "class":
<html>
	<head>
		<script language="JavaScript" type="text/javascript" src="calendar.js"></script>
		<script>
			var __calendars = new Array();
			__calendars.push(
				new Calendar(
					0,
					'_calendar0',
					new Date( (new Date()).getMonth() + '/' + (new Date()).getDate() + '/' + (new Date()).getFullYear() ),
					new Date( ((new Date()).getMonth() + 2) + '/' + (new Date()).getDate() + '/' + (new Date()).getFullYear() )
				)
			);
			__calendars.push(
				new Calendar(
					1,
					'_calendar1',
					new Date( (new Date()).getMonth() + '/' + (new Date()).getDate() + '/' + (new Date()).getFullYear() ),
					new Date( ((new Date()).getMonth() + 2) + '/' + (new Date()).getDate() + '/' + (new Date()).getFullYear() )
				)
			);
			function initCal() {
				// draw the current month's calendar
				document.getElementById('calContainer1').innerHTML = __calendars[0].drawCal(0);
				document.getElementById('calContainer2').innerHTML = __calendars[1].drawCal(0);
			}
		</script>
	</head>
	<body style="background:#ff9900;" onLoad="initCal();" topmargin=0 bottommargin=0 leftmargin=0 rightmargin=0 marginheight=0 marginwidth=0>
		<span id="calContainer1">Loading...</span><span id="calContainer2">Loading...</span>
	</body>
</html>

The source for the Calendar Javascript "class":
var __monthslong = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var __monthsshort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

function Calendar (arrayID, uniqueName, dateFrom, dateTo) {
	this.currDate = new Date();
	this.uniqueName = uniqueName;
	this.arrayID = arrayID;
	this.products = new Array();
	this.selectedDays = new Array();
	this.prodDesc = '';
	this.dateFrom = dateFrom;
	this.dateTo = dateTo;
	this.startTally = new Array();
	this.maxDays = 10;
}

Calendar.prototype.drawCal = function (nudgeMonth, alreadyLoaded) {

	this.currDate.setMonth((this.currDate.getMonth() + nudgeMonth));
	var tmpNextMo = new Date();
	tmpNextMo.setDate(1);
	tmpNextMo.setFullYear(this.currDate.getFullYear());
	tmpNextMo.setMonth(parseInt(this.currDate.getMonth()) + 1);

/* flawed next month display above calendar - 
 * if next month has less days than first month, 
 * it'll skip the correct month */
//	var tmpNextMo = new Date(this.currDate.getFullYear(), (this.currDate.getMonth() + 1), this.currDate.getDate());

	var _currDate = new Date();
	_currDate.setFullYear(this.currDate.getFullYear());
	_currDate.setMonth(this.currDate.getMonth());
	
	var calStr = 
		'<table cellpadding=0 cellspacing=0 border=0>' +
			'<tr>' +
				'<td style="background:url(ul.gif) no-repeat; height:6px;"><img width="1" height="6" border=0 src="_blank.gif"></td>' +
				'<td style="background:url(ur.gif) right top no-repeat; height:6px; width:6px;"><img width="6" height="6" border=0 src="_blank.gif"></td>' +
			'</tr>' +
			'<tr>' +
				'<td style="background:url(ml.gif) left top repeat-y; text-align:center; font-family:Tahoma; font-size:8pt; color:#808080; overflow:auto; padding-left:6px;" id="__calMessage">' +
					'<table cellpadding=0 cellspacing=0 border=0>' +
						'<tr>' +
							'<td width="100%" colspan=2 align=middle style="padding-bottom:5px;">' +
								'<table cellpadding=0 cellspacing=0 border=0 width="100%">' +
									'<tr>' +
										'<td align=left style="font-size:8pt; color:#000000; text-align:left; cursor:pointer;" ' +
											'onClick="__calendars[' + this.arrayID + '].drawCal((-1), true);">' +
											'<img border=0 src="_left.gif">' +
										'</td>' +
										'<td width="50%" align=left style="padding:0px 16px 0px 0px; font-size:8pt; color:#000000; text-align:center; cursor:default;">' +
											'<nobr>' + __monthslong[this.currDate.getMonth()] + ' ' + this.currDate.getFullYear() + '</nobr>' + 
										'</td>' +
										'<td width="50%" align=right style="padding:0px 0px 0px 16px; font-size:8pt; color:#000000; text-align:center; cursor:default;">' +
											'<nobr>' + __monthslong[tmpNextMo.getMonth()] + ' ' + tmpNextMo.getFullYear() + '</nobr>' +
										'</td>' +
										'<td align=right style="font-size:8pt; color:#000000; text-align:right; cursor:pointer;" ' +
											'onClick="__calendars[' + this.arrayID + '].drawCal(1, true);">' +
											'<img border=0 src="_right.gif">' +
										'</td>' +
									'</tr>' +
								'</table>' +
							'</td>' +
						'</tr>' +
						'<tr>' +
							'<td valign=top>' +
								'<table cellpadding=0 cellspacing=0 border=0>' +
									'<tr>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">S</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">M</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">T</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">W</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">T</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">F</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">S</td>' +
									'</tr>' +
									'<tr>';

	// set to the first day
	this.currDate.setDate(1);

	var pMonth = new Date();
	pMonth.setFullYear(this.currDate.getFullYear());
	pMonth.setMonth(this.currDate.getMonth());
	pMonth.setDate((this.currDate.getDate() - 1));
	
	// get current month days count
	var dCount = new Date();
	dCount.setDate(1);
	dCount.setFullYear(this.currDate.getFullYear());
	dCount.setMonth((this.currDate.getMonth() + 1));
	dCount.setDate(dCount.getDate() - 1);
/* FLAWED
	var dCount = new Date();
	dCount.setFullYear(this.currDate.getFullYear());
	dCount.setMonth((this.currDate.getMonth() + 1));
	dCount.setDate((this.currDate.getDate() - 1));
*/
	var dayCounter = 0;

	// perform the previous months last days
	for (var pMo=1; pMo <= this.currDate.getDay(); pMo++) {
		calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; color:#AAAAAA; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:default;">' + 
								(pMonth.getDate() - this.currDate.getDay() + pMo) + 
							'</td>';
		dayCounter++;
	}

	// now display all days in current month
	for (var cMD=0; cMD < dCount.getDate(); cMD++) {
			
			var validFrom = true;
			// find if this is a valid day or not
			if (typeof(this.dateFrom) != 'undefined') {
				if (this.dateFrom.getTime() > (new Date((this.currDate.getMonth() + 1) + '/' + (this.currDate.getDate() + cMD) + '/' + this.currDate.getFullYear())).getTime()) {
					validFrom = false;
				}
			}
			var validTo = true;
			// find if this is a valid day or not
			if (typeof(this.dateTo) != 'undefined') {
				if (this.dateTo.getTime() < (new Date((this.currDate.getMonth() + 1) + '/' + (this.currDate.getDate() + cMD) + '/' + this.currDate.getFullYear())).getTime()) {
					validTo = false;
				}
			}
			var dayInList = false;
			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == this.currDate.getFullYear() && 
					this.selectedDays[dInd][1] == this.currDate.getMonth() && 
					this.selectedDays[dInd][2] == (this.currDate.getDate() + cMD)) {
					dayInList = true;
				}
			}
			
			if (validFrom == false || validTo == false) {
				calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; background:#EFEFEF; font-size:8pt; color:#808080; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:default;';
			} else {
				calStr +=
							'<td id="__cal' + this.arrayID + '__' + this.currDate.getFullYear() + '-' + this.currDate.getMonth() + '-' + (this.currDate.getDate() + cMD) + '" ' +
								'align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:pointer;';
				if (dayInList) {
					calStr += ' background:#006699; color:#FFFFFF;';
				} else {
					calStr += ' background:#FFFFFF; color:#808080;';
				}
			}

			if (validFrom == false || validTo == false) {
				calStr += ' text-decoration:line-through;';
			}

			if (validFrom == true && validTo == true) {
				calStr += '" ' +
								'onMouseOver="__calendars[' + this.arrayID + '].toggleDayHilight([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, true);" ';
			}

			if (validFrom == true && validTo == true) {
//								'onClick="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'up\');">' + 
				calStr += '" ' +
								'onMouseOut="__calendars[' + this.arrayID + '].toggleDayHilight([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, false);" ' +
								'onMouseDown="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'down\');" ' + 
								'onMouseUp="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'up\');">' + 
								(this.currDate.getDate() + cMD) + 
							'</td>';
			} else {
				calStr += '">' + (this.currDate.getDate() + cMD) + '</td>';
			}
		
		dayCounter++;
		
		if ((dayCounter % 7) == 0) {
			calStr +=
						'</tr>';
			if (cMD != dCount.getDate()) {
						'<tr>';
			}
		}
	}

	// display the tail end of the month
	for (var eMo=1; eMo <= (6 - dCount.getDay()); eMo++) {
		calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; color:#AAAAAA; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #FFFFFF; text-align:center; cursor:default;">' + 
								eMo + 
							'</td>';
		dayCounter++;
	}
	
	calStr +=
								'</table>' +
							'</td>' +
							'<td valign=top style="padding-left:5px;">' +
								'<table cellpadding=0 cellspacing=0 border=0>' +
									'<tr>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">S</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">M</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">T</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">W</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">T</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">F</td>' +
										'<td align=middle style="background:#EEEEEE url(lightgray-gradient-ee.gif) bottom repeat-x; padding:1px; font-family:Tahoma; font-size:7pt; color:#000000; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; border-top:1px solid #EEEEEE; text-align:center; cursor:default;">S</td>' +
									'</tr>' +
									'<tr>';

	// set to the first day
	this.currDate.setMonth((this.currDate.getMonth() + 1));
	this.currDate.setDate(1);

	var pMonth = new Date();
	pMonth.setFullYear(this.currDate.getFullYear());
	pMonth.setMonth(this.currDate.getMonth());
	pMonth.setDate((this.currDate.getDate() - 1));
	
	// get current month days count
	var dCount = new Date();
	dCount.setDate(1);
	dCount.setFullYear(this.currDate.getFullYear());
	dCount.setMonth((this.currDate.getMonth() + 1));
	dCount.setDate(dCount.getDate() - 1);
/* FLAWED
	var dCount = new Date();
	dCount.setFullYear(this.currDate.getFullYear());
	dCount.setMonth((this.currDate.getMonth() + 1));
	dCount.setDate((this.currDate.getDate() - 1));
*/
	var dayCounter = 0;

	// perform the previous months last days
	for (var pMo=1; pMo <= this.currDate.getDay(); pMo++) {
		calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; color:#AAAAAA; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:default;">' + 
								(pMonth.getDate() - this.currDate.getDay() + pMo) + 
							'</td>';
		dayCounter++;
	}

	// now display all days in current month
	for (var cMD=0; cMD < dCount.getDate(); cMD++) {
			
			var validFrom = true;
			// find if this is a valid day or not
			if (typeof(this.dateFrom) != 'undefined') {
				if (this.dateFrom.getTime() > (new Date((this.currDate.getMonth() + 1) + '/' + (this.currDate.getDate() + cMD) + '/' + this.currDate.getFullYear())).getTime()) {
					validFrom = false;
				}
			}
			var validTo = true;
			// find if this is a valid day or not
			if (typeof(this.dateTo) != 'undefined') {
				if (this.dateTo.getTime() < (new Date((this.currDate.getMonth() + 1) + '/' + (this.currDate.getDate() + cMD) + '/' + this.currDate.getFullYear())).getTime()) {
					validTo = false;
				}
			}
			var dayInList = false;
			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == this.currDate.getFullYear() && 
					this.selectedDays[dInd][1] == this.currDate.getMonth() && 
					this.selectedDays[dInd][2] == (this.currDate.getDate() + cMD)) {
					dayInList = true;
				}
			}
			
			if (validFrom == false || validTo == false) {
				calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; background:#EFEFEF; font-size:8pt; color:#808080; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:default;';
			} else {
				calStr +=
							'<td id="__cal' + this.arrayID + '__' + this.currDate.getFullYear() + '-' + this.currDate.getMonth() + '-' + (this.currDate.getDate() + cMD) + '" ' +
								'align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #EEEEEE; text-align:center; cursor:pointer;';
				if (dayInList) {
					calStr += ' background:#006699; color:#FFFFFF;';
				} else {
					calStr += ' background:#FFFFFF; color:#808080;';
				}
			}

			if (validFrom == false || validTo == false) {
				calStr += ' text-decoration:line-through;';
			}

			if (validFrom == true && validTo == true) {
				calStr += '" ' +
								'onMouseOver="__calendars[' + this.arrayID + '].toggleDayHilight([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, true);" ';
			}

			if (validFrom == true && validTo == true) {
//								'onClick="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'up\');">' + 
				calStr += '" ' +
								'onMouseOut="__calendars[' + this.arrayID + '].toggleDayHilight([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, false);" ' +
								'onMouseDown="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'down\');" ' + 
								'onMouseUp="__calendars[' + this.arrayID + '].toggleDay([' + this.currDate.getFullYear() + ',' + this.currDate.getMonth() + ',' + (this.currDate.getDate() + cMD) + '], this, \'up\');">' + 
								(this.currDate.getDate() + cMD) + 
							'</td>';
			} else {
				calStr += '">' + (this.currDate.getDate() + cMD) + '</td>';
			}
		
		dayCounter++;
		
		if ((dayCounter % 7) == 0) {
			calStr +=
						'</tr>';
			if (cMD != dCount.getDate()) {
						'<tr>';
			}
		}
	}

	// display the tail end of the month
	for (var eMo=1; eMo <= (6 - dCount.getDay()); eMo++) {
		calStr +=
							'<td align=middle style="padding:2px; font-family:Tahoma; font-size:8pt; color:#AAAAAA; border-bottom:1px solid #CCCCCC; border-right:1px solid #CCCCCC; border-left:1px solid #FFFFFF; text-align:center; cursor:default;">' + 
								eMo + 
							'</td>';
		dayCounter++;
	}
	
	calStr +=
								'</table>' +
							'</td>' +
						'</tr>' +
						'<tr>' +
							'<td colspan=2 id="__cal' + this.arrayID + '__message" align="left" style="font-family:Tahoma; font-size:7pt; padding:2px;">' +
								'Select a date from the calendar above' +
							'</td>' +
						'</tr>' +
					'</table>' +
				'</td>' +
				'<td style="background:url(mr.gif) right top repeat-y; height:6px;"><img width="1" height="6" border=0 src="_blank.gif"></td>' +
			'</tr>' +
			'<tr>' +
				'<td style="background:url(bl.gif) no-repeat; height:6px;"><img width="1" height="6" border=0 src="_blank.gif"></td>' +
				'<td style="background:url(br.gif) right top no-repeat; height:6px; width:6px;"><img width="6" height="6" border=0 src="_blank.gif"></td>' +
			'</tr>' +
		'</table>';
		
	this.currDate.setMonth((this.currDate.getMonth() - 1));

	if (typeof(alreadyLoaded) != 'undefined' && alreadyLoaded == true) {
		document.getElementById(this.uniqueName).innerHTML = calStr;
	} else {
		return '<div id="' + this.uniqueName + '">' + calStr + '</div>';
	}
	
}

Calendar.prototype.toggleDay = function (day, divBlock, buttonEvent) {

	var dayInList = (-1);

	for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
		if (this.selectedDays[dInd][0] == day[0] && 
			this.selectedDays[dInd][1] == day[1] && 
			this.selectedDays[dInd][2] == day[2]) {
			dayInList = dInd;
		}
	}

	if (dayInList == (-1) && this.startTally.length == 0 && buttonEvent == 'down') {
	
		divBlock.style.background='#006699';
		divBlock.style.color='#FFFFFF';
		this.selectedDays.push(day);
		this.startTally = day;
		this.toggleDayHilight(day, divBlock, true);
		
		this.selectedDays.sort(function(a, b) { return (new Date(a[0], a[1], a[2])).getTime() - (new Date(b[0], b[1], b[2])).getTime() })
		
	} else if (this.startTally.length > 0 && buttonEvent == 'up') {

		var countDays = 1;
		var tallyDest = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
		var tallyNew = new Date(this.startTally[0], (parseInt(this.startTally[1]) + 0), this.startTally[2]);
		var tallyPivot = new Date(this.startTally[0], (parseInt(this.startTally[1]) + 0), this.startTally[2]);

		while (tallyNew.getFullYear() != tallyDest.getFullYear() ||
			tallyNew.getMonth() != tallyDest.getMonth() ||
			tallyNew.getDate() != tallyDest.getDate()) {

			countDays++;

			if (tallyNew.getTime() < tallyDest.getTime()) {
				tallyNew.setDate(tallyNew.getDate() + 1);
			} else {
				tallyNew.setDate(tallyNew.getDate() - 1);
			}

			var newDayInList = false;
			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == tallyNew.getFullYear() && 
					this.selectedDays[dInd][1] == tallyNew.getMonth() && 
					this.selectedDays[dInd][2] == tallyNew.getDate()) {
					newDayInList = true;
				}
			}

			var newDivBlock = document.getElementById('__cal' + this.arrayID + '__' + tallyNew.getFullYear() + '-' + tallyNew.getMonth() + '-' + tallyNew.getDate());

			if (!newDayInList && countDays <= this.maxDays && newDivBlock) {
				this.selectedDays.push([tallyNew.getFullYear(), tallyNew.getMonth(), tallyNew.getDate()]);

				this.selectedDays.sort(function(a, b) { return (new Date(a[0], a[1], a[2])).getTime() - (new Date(b[0], b[1], b[2])).getTime() })

				newDivBlock.style.background='#006699';
				newDivBlock.style.color='#FFFFFF';
			} else if (!newDayInList && newDivBlock) {
				newDivBlock.style.background='#FFFFFF';
				newDivBlock.style.color='#808080';
			}
		}

		this.startTally = new Array();

	} else if (buttonEvent == 'up') {

		// if first day - remove whole block...
		var tallyNew = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
		var tallyPrev = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
		tallyPrev.setDate(tallyPrev.getDate() - 1);
		var prevDayInList = false;
		var remDayInList = true;
		var remDayInListInd = (-1);

		for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
			if (this.selectedDays[dInd][0] == tallyPrev.getFullYear() && 
				this.selectedDays[dInd][1] == tallyPrev.getMonth() && 
				this.selectedDays[dInd][2] == tallyPrev.getDate()) {
				prevDayInList = true;
			}
		}

		if (!prevDayInList) {
			while (remDayInList) {

				tallyNew.setDate(tallyNew.getDate() + 1);

				var newDivBlock = document.getElementById('__cal' + this.arrayID + '__' + tallyNew.getFullYear() + '-' + tallyNew.getMonth() + '-' + tallyNew.getDate());

				remDayInList = false;
				for (var rdInd=0; rdInd < this.selectedDays.length; rdInd++) {
					if (this.selectedDays[rdInd][0] == tallyNew.getFullYear() && 
						this.selectedDays[rdInd][1] == tallyNew.getMonth() && 
						this.selectedDays[rdInd][2] == tallyNew.getDate()) {
						remDayInListInd = rdInd;
						remDayInList = true;
					}
				}

				if (remDayInList) {
					this.selectedDays.splice(remDayInListInd, 1);
					
					this.selectedDays.sort(function(a, b) { return (new Date(a[0], a[1], a[2])).getTime() - (new Date(b[0], b[1], b[2])).getTime() })
					
					this.toggleDayHilight([tallyNew.getFullYear(), tallyNew.getMonth(), tallyNew.getDate()], newDivBlock, false);
				}

			}
		}

		if (dayInList != (-1)) {
			this.selectedDays.splice(dayInList, 1);

			this.selectedDays.sort(function(a, b) { return (new Date(a[0], a[1], a[2])).getTime() - (new Date(b[0], b[1], b[2])).getTime() })

			this.toggleDayHilight(day, divBlock, false);
		}

		this.startTally = new Array();
		
	}

}

Calendar.prototype.toggleDayHilight = function (day, divBlock, toggle) {
	// clear selection
	(document.selection) ? document.selection.empty() : window.getSelection().removeAllRanges();
	
	var userMsg = ' ';

	var dayInList = false;
	for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
		if (this.selectedDays[dInd][0] == day[0] && 
			this.selectedDays[dInd][1] == day[1] && 
			this.selectedDays[dInd][2] == day[2]) {
			dayInList = true;
		}
	}

	if (!dayInList && toggle) {
		if (this.startTally.length > 0) {

			// overflow highlighting 
			divBlock.style.background = '#E3E3E3';
			divBlock.style.color = '#FFFFFF';

		} else {
			divBlock.style.background = '#008FD6';
			divBlock.style.color = '#FFFFFF';
			userMsg = 'Add ' + __monthsshort[day[1]] + ' ' + day[2] + ', ' + day[0];
		}
	} else if (dayInList && this.startTally.length > 0) {
		userMsg = 'Click Again to add ' + __monthsshort[day[1]] + ' ' + day[2] + ', ' + day[0];
	} else if (dayInList && this.startTally.length == 0) {

		if (toggle) {

			divBlock.style.background = 'red';
			divBlock.style.color = '#FFFFFF';
			
			// if first day - remove whole block...
			var tallyNew = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
			var tallyPrev = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
			tallyPrev.setDate(tallyPrev.getDate() - 1);
			var prevDayInList = false;
			var remDayInList = true;
			var countDays = 1;

			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == tallyPrev.getFullYear() && 
					this.selectedDays[dInd][1] == tallyPrev.getMonth() && 
					this.selectedDays[dInd][2] == tallyPrev.getDate()) {
					prevDayInList = true;
				}
			}

			if (!prevDayInList) {
				while (remDayInList) {

					tallyNew.setDate(tallyNew.getDate() + 1);

					var newDivBlock = document.getElementById('__cal' + this.arrayID + '__' + tallyNew.getFullYear() + '-' + tallyNew.getMonth() + '-' + tallyNew.getDate());

					remDayInList = false;
					for (var rdInd=0; rdInd < this.selectedDays.length; rdInd++) {
						if (this.selectedDays[rdInd][0] == tallyNew.getFullYear() && 
							this.selectedDays[rdInd][1] == tallyNew.getMonth() && 
							this.selectedDays[rdInd][2] == tallyNew.getDate()) {
							remDayInList = true;
						}
					}
					
					if (remDayInList) {
						newDivBlock.style.background = 'red';
						newDivBlock.style.color = '#FFFFFF';
						countDays++;
					}
				
				}
			}

			if (countDays > 1) {
				// nudge the tally check date back one since the iterrator will be on the day that failed to be in the cart
				tallyNew.setDate(tallyNew.getDate() - 1);
				var startDate = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
				userMsg = 'Click to remove ' + countDays + ' days from ' + __monthsshort[startDate.getMonth()] + ' ' + startDate.getDate() + ', ' + startDate.getFullYear() +
					' to ' + __monthsshort[tallyNew.getMonth()] + ' ' + tallyNew.getDate() + ', ' + tallyNew.getFullYear();
			} else {
				userMsg = 'Click to remove ' + __monthsshort[day[1]] + ' ' + day[2] + ', ' + day[0];
			}
		
		} else {
		
			divBlock.style.background = '#006699';
			divBlock.style.color = '#FFFFFF';

			// clear the sequence that was queued for deletion
			var tallyNew = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
			var tallyPrev = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
			tallyPrev.setDate(tallyPrev.getDate() - 1);
			var prevDayInList = false;
			var remDayInList = true;

			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == tallyPrev.getFullYear() && 
					this.selectedDays[dInd][1] == tallyPrev.getMonth() && 
					this.selectedDays[dInd][2] == tallyPrev.getDate()) {
					prevDayInList = true;
				}
			}

			if (!prevDayInList) {
				while (remDayInList) {

					tallyNew.setDate(tallyNew.getDate() + 1);

					var newDivBlock = document.getElementById('__cal' + this.arrayID + '__' + tallyNew.getFullYear() + '-' + tallyNew.getMonth() + '-' + tallyNew.getDate());

					remDayInList = false;
					for (var rdInd=0; rdInd < this.selectedDays.length; rdInd++) {
						if (this.selectedDays[rdInd][0] == tallyNew.getFullYear() && 
							this.selectedDays[rdInd][1] == tallyNew.getMonth() && 
							this.selectedDays[rdInd][2] == tallyNew.getDate()) {
							remDayInList = true;
						}
					}

					if (remDayInList) {
						newDivBlock.style.background = '#006699';
						newDivBlock.style.color = '#FFFFFF';
					}

				}
			}

			
		}

	} else if (!dayInList && !toggle) {
		divBlock.style.background = '#FFFFFF';
		divBlock.style.color = '#808080';
	}
	
	var countDays = 1;
	var maxWarning = false;
	if (this.startTally.length > 0) {

		var tallyDest = new Date(day[0], (parseInt(day[1]) + 0), day[2]);
		var tallyNew = new Date(this.startTally[0], (parseInt(this.startTally[1]) + 0), this.startTally[2]);
		var tallyPivot = new Date(this.startTally[0], (parseInt(this.startTally[1]) + 0), this.startTally[2]);

		while (tallyNew.getFullYear() != tallyDest.getFullYear() ||
			tallyNew.getMonth() != tallyDest.getMonth() ||
			tallyNew.getDate() != tallyDest.getDate()) {
			
			countDays++;

			if (tallyNew.getTime() < tallyDest.getTime()) {
				tallyNew.setDate(tallyNew.getDate() + 1);
			} else {
				tallyNew.setDate(tallyNew.getDate() - 1);
			}

			var newDivBlock = document.getElementById('__cal' + this.arrayID + '__' + tallyNew.getFullYear() + '-' + tallyNew.getMonth() + '-' + tallyNew.getDate());
				
			dayInList = false;
			for (var dInd=0; dInd < this.selectedDays.length; dInd++) {
				if (this.selectedDays[dInd][0] == tallyNew.getFullYear() && 
					this.selectedDays[dInd][1] == tallyNew.getMonth() && 
					this.selectedDays[dInd][2] == tallyNew.getDate()) {
					dayInList = true;
				}
			}

			if (countDays <= this.maxDays) {
				if (newDivBlock) {
					if (!dayInList && toggle) {
						newDivBlock.style.background='#008FD6';
						newDivBlock.style.color='#FFFFFF';
					} else if (!dayInList && !toggle) {
						newDivBlock.style.background='#FFFFFF';
						newDivBlock.style.color='#808080';
					}
				}

				if ((new Date(day[0], (parseInt(day[1]) + 0), day[2])).getTime() < tallyPivot.getTime()) {
					userMsg = 'Click to add ' + countDays + ' days from ' + __monthsshort[tallyNew.getMonth()] + ' ' + tallyNew.getDate() + ', ' + tallyNew.getFullYear() + ' to ' + __monthsshort[tallyPivot.getMonth()] + ' ' + tallyPivot.getDate() + ', ' + tallyPivot.getFullYear();
				} else {
					userMsg = 'Click to add ' + countDays + ' days from ' + __monthsshort[tallyPivot.getMonth()] + ' ' + tallyPivot.getDate() + ', ' + tallyPivot.getFullYear() + ' to ' + __monthsshort[tallyNew.getMonth()] + ' ' + tallyNew.getDate() + ', ' + tallyNew.getFullYear();
				}

			} else if (newDivBlock) {
				if (!dayInList && toggle) {
				
					// overflow highlighting 
					newDivBlock.style.background='#EEEEEE';
					newDivBlock.style.color='#808080';

					if (!maxWarning) {
						userMsg += '<BR>Cannot choose more than ' + this.maxDays + ' days.'
						maxWarning = true;
					}
				} else if (!dayInList && !toggle) {
					newDivBlock.style.background='#FFFFFF';
					newDivBlock.style.color='#808080';
				}
			}

		}
			
	}
	document.getElementById('__cal' + this.arrayID + '__message').innerHTML = userMsg;
}

Here are the images used in the source (SHIFT+CLICK to download):
comments
comment
08/17/2007 11:16AM | spit
Fantastic script !!

I spent ages searching for a date-picker that didn't have loads of JS libraries, etc, and this was spot on.

One minor thing - could you please also post the background gif for the elegant rounded-corner grey square behind the calendar?

(I believe it's "/images/readon_black.gif" but we can't download that part).

Thanks again !

comment
08/27/2007 06:51PM | Jim Palmer
Thanks!

I added direct links to all the images used in the source at the bottom of the post. The rounded-corner grey square is an auto-sizing table that I used for my tooltips DHTML project.

comment
08/28/2007 04:03AM | spit
Perfect - thank you !

comment
08/31/2007 01:59PM | P Dillinger
Nice date picker. It's a good base to work off of for a project I'm doing. Just wanted to say thinks, and link added.

comment
08/31/2007 04:39PM | Jim Palmer
Just updated a Fix to the display of August 2007 month with 8/30 (today) as the current date - was throwing off the days that "trail" the current month.

I simply added line 76 to the calendar.js file above to decrement the dCount Date object back a month. This should not adversely affect your code - but suggested to patch it!

comment
10/31/2007 04:23PM | K. Ng
Found a bug on the calendar codes while trying to use it to implement a project. The dates calculation messed up. Try to run it today "10/31/2007" or set the date on your OS to 10/31/2007 and you'll see what I mean.

comment
10/31/2007 06:11PM | Jim Palmer
K. Ng - good catch. I noticed it working with this current month in my testbed.

I've added lots of features to this calendar to make it more of a kludge than the first version - which adds to the risk of something coded improperly.

I fixed the above bug by added a new tmpNextMo declaration near the top of the calendar.js drawCal function. I left the flawed bit of code in there to help with the change. The problem arose where if the first month, i.e. the current month, had more days than the next month it would skip the next month. In this case Oct has more days than Nov hence why it skipped to Dec.

I'll probably post another version of this - seeing as I've been fixing and changing the interactivity of this code - and made it less of a kludge.

Thanks!

comment
03/31/2008 02:34PM | Shawn
very clean calendar, best DIV cal I've found.. one more nit, a left over bug from the Mar/April i believe.. The April 08 is reporting the tail end of March as ending on the 30th, great product.. thanks so much.

comment
09/17/2008 12:47PM | David
Hi!
I would like to know how to set or unset programmatically one or more dates.
And I would like to know how to read set dates to store, for example, in a database.

Thank you very much!

comment
02/03/2009 12:10PM | Noah
Hi!
I would like to know how to set or unset programmatically one or more dates.
And I would like to know how to read set dates to store, for example, in a database.

comment
03/08/2011 08:48PM | jamie
This looks like the beginnings of EXACTLY the type of selector I need... except... (don't you hate those?):

1. I'd like to be able to create a grid of months, user-selectable layout (1x12, 3x4, 4x3, 2x6, 3x3 with selectable start/end months, etc.).

2. I'd really like this to be a jQuery-compatible thing. Doesn't have to be a plug-in, per se, but should use $("#id").val() instead of .innerHTML, or some such for cross-browser compatibility.

Have you had any thoughts for those types of changes?

I've looked at your other date-picker that _is_ a jQuery plug-in, but prefer the behavior of this one, instead.

new comment
NAME
EMAIL ME ON UPDATES
EMAIL (hidden)
URL
MESSAGE TAGS ALLOWED: <code> <a> <pre class="code [tab4|tabX|inline|bash]"> <br>
PREVIEW COMMENT
TURING TEST
gravatar