
/*

Calendar() Class
v 1.1
(c) 2010 Jon Ray
jon@navigationadvertising.com



= = = Calendar() Class Manual = = =

--------------------------
- Constructor Parameters -
--------------------------
The Calendar() class constructor requires only one parameter:
the name of the instance of the class. Example:
calendar1 = new Calendar("calendar1");

You MUST pass the name of the new declared instance to the
constructor or the class' prevM(), nextM(), prevY(), and nextY()
methods will not be able to reference the correct class and will
abort on error.

Optional parameters passed to the Calendar() class are the
year and month for the calendar to be defaulted to. Declaring:

calendar1 = new Calendar("calendar1", 2010, 10);

will result in a new Calendar class with an instance name of
"calendar1" with the year being set to 2010 and the month
being set to November. Please note that Javascript months
start with 0 and end with 11.

-----------------
- Class Methods -
-----------------
The Calendar() class has 5 accessible methods:

setToday(yyyy, mm, dd)
setEvents(dateArray)
setLinks(linkArray)
errors()
build()

setToday(yyyy, mm, dd):
This method allows you to set the date reference for day. Since
Javascript is a client-side language, you have no control over
the default date the client might have set of their machine,
therefore it is sometimes advantageous to set a pre-defined
reference for today, especially if your site is in a different
time-zone than those visiting it and you want it to reference
the local time. The time is set in a yyyy, mm, dd format.
Remember that Javascript months start at 0 (January) and end at
11 (December). This method should be called BEFORE the build()
method.

setEvents(dateArray):
This method allows you to pass an array of Javascript Date()
objects to an instance of the class, where they will be displayed
in link format per the private method createLink(). This needs
to be done BEFORE the build() method is called, otherwise the
events will not be initialized until after the first calendar
display is printed. 

setLinks(linkArray):
This method allows you to pass an array of links to the class
instance, which are then tied to the events array passed by
setEvents(). setEvents() must be called before setLinks().
If link array item is missing when the link array is being
paired with the date array, the date will be assigned an
empty string as a link. If more link items are in the link
array than are date items in the date array, an error will
occur and the links will only be assigned up to the last date
item in the date array, with the trailing link array items
being deleted. This method should be called AFTER the
setEvents() method and BEFORE the build() method.

errors():
This method turns on error messages.

build():
This method will build the calendar when called. It accepts no
parameters and should only be called once after all other initial
methods have been called.

-------------------------------
- Calendar Layout through CSS -
-------------------------------
To aid in the layout of the Calendar class, all major objects and
elements in the printed calendar have been assigned CSS classes
and/or ID's. If CSS is not a viable option, customization of the
printed calendar can be done by directly editing the HTML output
in the build() method. Please note that editing the HTML directly
may cause the CSS selectors to stop working.

The CSS classes are as follows:

- calendar-<calendarInstance> : The div ID of div containing the
calendar,where <calendarInstance> is the name of the instance of the
class. If the calendar's instance name is "calendar1", then the div
id would be calendar-calendar1.

- calendar-table: The class of the table the calendar is displayed in.

- calendar-days: The class of the table-row containing the days of
the week.

- sunday, monday, tuesday, wednesday, thursday, friday, saturday, sunday:
The classes of the respective table cells containing the days of the
week at the top of each calendar.

- date: The class assigned to all dates - useful for referencing the
table cells containing dates.

- date-active: The class assigned to all dates currently being
viewed on the calendar that fall within the current month.

- date-inactive: The class assigned to all dates currently being
viewed on the calendar that fall outside of the current month.

- date-today: The class assigned to any date that corresponds with
"today's" date.

- date-event: The class assigned to all dates on the calendar for which
there is an associated event.

- date-link: The class assigned to all dates on the calendar for which
there is an associated link.

= = = End of Calendar() Class Manual = = =

*/




// class constructor
function Calendar (instance, year, month) {

	// Public Methods
	// method to set the date for "today"
	this.setToday = function(year, month, date) {
		if (checkDate(year, month, date)) {
			today = new Date (year, month, date);
		}
		else {
			error('Invalid date ('+year+'/'+month+'/'+date+') provided for '+this.instance+'.setToday().\r\nToday date for '+this.instance+' set for '+ today);
		}
	}
	
	// method to create dates to be highlighted
	// this method is passed an array of date
	// objects to be highlighted on the calendar
	this.setEvents = function (dateArray) {
		if (dateArray && dateArray.length > 0) {
			for (i = 0; i < dateArray.length; i++) {
				events[i] = new Date(dateArray[i]);
			}
		}
	}
	
	// method to assign links to date items
	// this method is passed an array of text links
	this.setLinks = function (linkArray) {
		// check that link array is not empty 
		if (linkArray && linkArray.length > 0) {
			// cycle through array
			for (i = 0; i < events.length; i++) {
				// assign link array empty string if passed array
				// is null or empty and call error
				if (linkArray[i] == "" || !linkArray[i]) {
					links[i] = "";
					error('setLinks() array item '+i+' was empty. Link value set to empty string.');
				}
				// otherwise assign link to links array
				else {
					links[i] = linkArray[i];
				}
			}
			
			// call error() if passed links array larger than dates array
			if (linkArray.length > events.length)
				error("Too many links passed to setLinks(). Excess links were deleted.");
		}
	}
	
	// method to toggle error messages
	this.errors = function() {
		showErrors = true;
	}
	
	// method to build calendar
	this.build = function() {
	
		// assign dates array to markedDates array
		for (i = 0; i < dates.length; i++) {
			markedDates[i] = new Array();
			markedDates[i]['date'] = new Date(dates[i]);
			markedDates[i]['event'] = false;
			
			if (events && events.length > 0) {
				for (c = 0; c < events.length; c++) {
					if (events[c].valueOf() == dates[i].valueOf()) {
						markedDates[i]['event'] = true;
						markedDates[i]['link'] = links[c];
						markedDates[i]['id'] = i;
					}
				}
			}
		}
	
		// create date references for shorter coding
		var days = dates.length;
		var weeks = days / 7;
		
		// create variable to hold html
		var content = "";
		
		content += '<table cellspacing="1" cellpadding="8" class="calendar-table">';
		content += '<tr>';

		// calendar header
		content += '<td class="calendar-header"><a href="javascript:'+this.instance+'.prevY();">&lsaquo;</a></td>';
		content += '<td class="calendar-header"><a href="javascript:'+this.instance+'.prevM();">&laquo;</a></td>';
		content += '<td colspan="3" class="calendar-header">';
		content += '&nbsp;'+ months[month] +'&nbsp;'+ year +'&nbsp;';
		content += '</td>';
		content += '<td class="calendar-header"><a href="javascript:'+this.instance+'.nextM();">&raquo;</a></td>';
		content += '<td class="calendar-header"><a href="javascript:'+this.instance+'.nextY();">&rsaquo;</a></td>';
		content += '</tr>';
		
		// calendar week display (SMTWTFS)
		content += '<tr class="calendar-days">';
		content += '<td align="center" class="sunday">S</td>';
		content += '<td align="center" class="monday">M</td>';
		content += '<td align="center" class="tuesday">T</td>';
		content += '<td align="center" class="wednesday">W</td>';
		content += '<td align="center" class="thurday">T</td>';
		content += '<td align="center" class="friday">F</td>';
		content += '<td align="center" class="saturday">S</td>';
		content += '</tr>';
		
		// build each week
		for (i = 0, c = 0; i < weeks; i++) {
			
			// start this current week's <TR>
			content += '<tr>';
			
			// build each day in current week
			for (n = 0; n < 7; c++, n++) {
				
				// create flag to set event status
				var eventFlag = false;
				// create flag to set link status
				var linkFlag = false;
				// create variable to hold date class
				var dateClass = "";
				
				// check if event on this date
				if (markedDates[c]['event'] == true) {
					eventFlag = true;
					
					// check if link exists for date
					if (markedDates[c]['link'] && markedDates[c]['link'] != "") {
						linkFlag = true;
					}
				}

				// create temporory variables to check current date
				var checkYear = markedDates[c]['date'].getFullYear();
				var checkMonth = markedDates[c]['date'].getMonth();
				var checkDate = markedDates[c]['date'].getDate();
				
				// check date and set appropriate classes
				// check if date is "today" (date-today class)
				if (checkYear == today.getFullYear() && checkMonth == today.getMonth() && checkDate == today.getDate())
					dateClass += " date-today ";
				
				// check if date is in this month (date-active class)
				if (month == checkMonth)
					dateClass += " date-active";
				
				// else assign date-inactive class
				else
					dateClass += " date-inactive";
				
				// if current date has event, add "link" class to class
				if (eventFlag)
					dateClass += " date-event";

				if (linkFlag)
					dateClass += " date-link";

				// write CSS class into html
				content += '<td align="center" class="'+ dateClass +'">';
				
				/*************************************************/
				
				// add link if current date has event
				if (linkFlag) {
					content += '<a href="javascript:'+this.instance+'.go('+markedDates[c]['id']+')">';
				}
				
				/*************************************************/
				
				// add current date object's date to the calendar
				content += markedDates[c]['date'].getDate();

				// close link if applicable
				if (linkFlag)
					content += '</a>';

				
				// close </TD>
				content += '</td>';
			}
			
			// close </TR>
			content += '</tr>';
		}
		
		// close </TABLE>
		content += '</table>';
		
		// update calendar div
		var calendarDiv = "calendar-" + this.instance;
		document.getElementById(calendarDiv).innerHTML = content;
	}
	
	// method to scroll to previous month
	this.prevM = function () {
		var tempYear = year;
		var tempMonth = month;
		
		tempMonth--;
		if (tempMonth < 0) {
			tempYear--;
			tempMonth = 11;
		}
		
		if (checkDate(tempYear, tempMonth, 1)) {
			year = tempYear;
			month = tempMonth;
			set (year, month);
			this.build();
			this.setBookmark();
		}
	}
	
	// method to scroll to next month
	this.nextM = function () {
		var tempYear = year;
		var tempMonth = month;
		
		tempMonth++;
		if (tempMonth > 11) {
			tempYear++;
			tempMonth = 0;
		}
		
		if (checkDate(tempYear, tempMonth, 1)) {
			year = tempYear;
			month = tempMonth;
			set (year, month);
			this.build();
			this.setBookmark();
		}
	}
	
	// method to scroll to previous year
	this.prevY = function () {
		var tempYear = year;
		var tempMonth = month;
		
		tempYear--;
		
		if (checkDate(tempYear, tempMonth, 1)) {
			year = tempYear;
			month = tempMonth;
			set (year, month);
			this.build();
			this.setBookmark();
		}
	}
	
	// method to scroll to next year
	this.nextY = function () {
		var tempYear = year;
		var tempMonth = month;
		
		tempYear++;
		
		if (checkDate(tempYear, tempMonth, 1)) {
			year = tempYear;
			month = tempMonth;
			set (year, month);
			this.build();
			this.setBookmark();
		}
	}

	// method to allow internal (shorthand) links for calendar events
	this.go = function (id) {
		// set cookie is refer flag is set
		this.setBookmark();
		document.location.href = markedDates[id]['link'];
	}
	
	// method to allow calendar month/year to be bookmarked
	this.setBookmark = function (year, month) {
		var expire = new Date();	
		
		if (year > -1 && month > -1) {
			dateObj.setYear(year);
			dateObj.setMonth(month);
			dateObj.setDate(1);
			expire.setTime(expire.getTime() + bookmarkExpire);
			document.cookie = ''+this.instance+'Bookmark='+dateObj.getTime()+'; expires=:'+expire.toGMTString()+'; path=/';
		}
		
		else {
			expire.setTime(expire.getTime() + bookmarkExpire);
			document.cookie = ''+this.instance+'Bookmark='+dateObj.getTime()+'; expires=:'+expire.toGMTString()+'; path=/';
		}
	}
	
	// method to check for Bookmark cookie
	 this.checkBookmark = function() {
		var bookmark = ''+this.instance + 'Bookmark' + '=';
	
		if (document.cookie.length) {
			var cookies = document.cookie.split(';');
	
			for (i=0;i < cookies.length;i++) {
				var current = cookies[i];
				
				while (current.charAt(0) == ' ')
					current = current.substring(1,current.length);
				
				if (current.indexOf(bookmark) == 0) {
					return current.substring(bookmark.length,current.length);
				}
			}
		}
		
		return null;
	}
	
	// method to expire bookmark cookie
	this.resetBookmark = function() {
		document.cookie = ''+this.instance+'Bookmark='+dateObj.getTime()+'; expires=:-1; path=/';	
	}
	
	// method to allow external link to be processed while saving calendar bookmark
	this.nav = function (a) {
		this.setBookmark();
		document.location.href = a;
	}
	
	
	// Private Methods
	// method to set dates for calendar
	function set (year, month) {
		
		// set month and year
		dateObj.setMonth(month);
		dateObj.setYear(year);
		dateObj.setDate(1);
		
		// calculate various references for dates
		var leadingDays = new Date(year, month, 1).getDay();
		var days = daysInMonth(year, month);
		var trailingDays = 0;
		var calLength = leadingDays + days + trailingDays;
		
		// determine required number of trailing days to make calendar fit in 7-day blocks
		if ((calLength) % 7 != 0) {
			do {
				trailingDays++;
			} while ((calLength = leadingDays + days + trailingDays) % 7 != 0);
		}

		var prevYear = year;
		var prevMonth = month - 1;
		
		if (! checkDate(prevYear, prevMonth, 1)) {
			prevYear--;
			prevMonth = 11;
		}
		
		var prevDays = daysInMonth(prevYear, prevMonth);
		
		var nextYear = year;
		var nextMonth = month + 1;
		
		if (! checkDate(nextYear, nextMonth, 1)) {
			nextYear++;
			nextMonth = 0;
		}
		
		var nextDays = daysInMonth(nextYear, nextMonth);
		
		// create new date array to hold dates
		dates = new Array();
		// define counter for date array
		var index = 0;
		
		// create dates for leading days before month
		for (i = 0; i < leadingDays; i++) {
			dates[index] = new Date(prevYear, prevMonth, prevDays-leadingDays+i+1);
			//alert(dates[index]);
			index++;
		}
		
		// create dates for days in month
		for (i = 0; i < days; i++) {
			dates[index] = new Date(year, month, i+1);
			//alert(dates[index]);
			index++;
		}
		
		// create dates for trailing days after month
		for (i = 0; i < trailingDays; i++) {
			dates[index] = new Date(nextYear, nextMonth, i+1);
			//alert(dates[index]);
			index++;
		}

		//alert("Calendar Diagnostics:\r\n- - - - - - - - - - - - -\r\n\Date: "+dateObj.getMonth() +'/'+dateObj.getDate()+'/'+dateObj.getFullYear()+"\r\nLeading Days: " +leadingDays+"\r\nDays In Month: "+days+"\r\nTrailing Days: "+trailingDays+"\r\nModulus: "+(leadingDays + days + trailingDays) % 7 +"\r\nWeeks: "+(calLength / 7)+"\r\nPrevious Month: "+prevMonth+", "+prevYear+"\r\nNext Month: "+nextMonth+", "+nextYear);
	}

	// method to return link to build() method
	function createLink(dateObj) {
		return (url+year+'-'+month+'-'+date);
	}

	// method to check for valid date using mm, dd, yyyy format
	function checkDate (year, month, date) {
		var dateObj=new Date(year, month, date);
		return ((date==dateObj.getDate()) && (month==dateObj.getMonth()) && (year==dateObj.getFullYear()));
	}

	// method to return days in month
	function daysInMonth (year, month) {
		return 32 - new Date(year, month, 32).getDate();
	}

	// method to display error messages
	function error(msg) {
		if (showErrors) {
			alert(msg);
		}
	}
	
	// ******************************************//

	
	// create ID for this instance of the class
	this.instance = instance;

	// get/set today's date
	var today = new Date();
	today.getDate();
	today.getMonth();
	today.getFullYear();
		
	// create months array for "printable" date
	var months = new Array();
	months[0] = "January";
	months[1] = "February";
	months[2] = "March";
	months[3] = "April";
	months[4] = "May";
	months[5] = "June";
	months[6] = "July";
	months[7] = "August";
	months[8] = "September";
	months[9] = "October";
	months[10] = "November";
	months[11] = "December";
	
	// create new date object for calendar
	var dateObj = new Date();
	// create date array 
	var dates = new Array();
	// create array to hold highlighted dates
	var events = new Array();
	// create array to hold links tied to events
	var links = new Array();
	// create marked array to hold array of arrays of highlighted dates
	var markedDates = new Array();
	
	// create error message flag
	var showErrors = false;
	
	// create bookmark flag. Set to false if you want the
	// calendar to reset to current date after navigating a link
	var bookmark = true;
	
	// variable to set bookmark cookie expiration time (in milliseconds)
	var bookmarkExpire = 900000;
	
	/***********************************/
	
	if (bookmark == true && (timestamp = this.checkBookmark())) {
		// change from string to int
		timestamp = parseInt(timestamp);
		var reference = new Date(timestamp);
		set (reference.getFullYear(), reference.getMonth());
		
	}
	
	else {
	
		// check if year is null and set default if necessary
		if (year == "" || ! year)
			year = today.getFullYear();
		
		// check if month is null and set default if necessary
		if (month == "" || ! month)
			month = today.getMonth();
		
		// check if valid date
		if (checkDate(year, month, 1)) {
			// set date to today
			dateObj.setMonth(month);
			dateObj.setYear(year);
		}
		
		// set today as default day if provided date not valid
		else {
			// set date 
			dateObj = today;
		}
	}

	/**********************************/

	// create date variables for dateObj
	var month = dateObj.getMonth();
	var year = dateObj.getFullYear();
	
	// write in div
	document.writeln('<div align="center" 	id="calendar-'+this.instance+'">');
	document.writeln('</div>');
	
	// initialize calendar dates
	set(year, month);
}

