﻿/* start date-extensions.js */
/*
	This has original and modified versions of Date and Range extensions.
	It includes:
	- A modified version of Date.js by Jason S. Kerchner 
		(see http://livingmachines.net/)
	- Modified Date.succ extension to support Range object by Matthew FOster.
		(see http://positionabsolute.net/blog/2007/09/prototype-object-range-calendar-extension.php)
	- Some original extensions (MonthRange class, toObject Date extension, etc.) to support YaCal
*/

/**
 * Extensions to the native JavaScript Date object.
 * 
 * "Static" members:
 * daysInMonth, dayNames, shortDayNames, dayChars
 * 
 * Instance members:
 * copy, getDayName, getShortDayName, getDayChar, getMonthName, getShortMonthName, 
 * getMonthChar, getMonthNumber, daysInMonth, addDays, addMonths, addYears, addHours,
 * addMinutes, addSeconds, clearTime, compareTo, isBefore, isAfter, equals, toFormat, 
 * toObject, succ
 * 
 * Extension to String object:
 * toDate
 */
 
/**
 * Full, short and single character names for the days of the week.
 */
Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
Date.shortDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
Date.dayChars = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];

/**
 * Full, short and single character names for the months.  Override these to provide 
 * multi-language support.
 */
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
Date.shortMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.monthChars = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];

/*!!! modification for Forza specific localization */
if(LocalizationInfo){
	Date.dayNames = LocalizationInfo.dayNames;
	Date.shortDayNames = LocalizationInfo.shortDayNames;
	Date.dayChars = LocalizationInfo.dayChars;
	Date.monthNames = LocalizationInfo.monthNames;
	Date.shortMonthNames = LocalizationInfo.shortMonthNames;
	Date.monthChars = LocalizationInfo.monthChars;
};

/**
 * Objects that give access to the month index from the full and short month
 * names.  This is faster that searching the monthNames or shortMonthNames arrays. 
 */
Date.monthNumbers = { };
Date.shortMonthNumbers = { };
for (var i = 0; i < 12; i++) {
	Date.monthNumbers[Date.monthNames[i]] = i;
	Date.shortMonthNumbers[Date.shortMonthNames[i]] = i;
}

/**
 * Returns the number of days in the current month and year.  Note that the 
 * month is zero-based, so January = 0, February = 1, etc.
 * @param {Number} month The month who's days are to be counted.
 * @param {Number} year The year of the to check, to allow for leap years.
 * @return {Number} The number of days in the given month for the given year.
 */
Date.daysInMonth = function(month, year){
	// If February, check for leap year
	if ((month == 1) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) {
		return 29;
	}
	else { //		J	F	M	A	M	J	J	A	S	O	N	D
		var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		return days[month];
	}
};

/**
 * Creates a copy of the current date object.  Assigning one date variable
 * to another simply points both variables to the same date object.  This
 * function is useful when you need a unique copy of the date object. 
 * @return {Date} A copy of this date object.
 */
Date.prototype.copy = function(){
	return new Date(this);
};

/**
 * @return {String} The full name of the day of the week.
 */
Date.prototype.getDayName = function(){
	return Date.dayNames[this.getDay()];
};

/**
 * @return {String} The short name of the day of the week.
 */
Date.prototype.getShortDayName = function(){
	return Date.shortDayNames[this.getDay()];
};

/**
 * @return {String} The single character representation of the day of the week.
 */
Date.prototype.getDayChar = function(){
	return Date.dayChars[this.getDay()];
};

/**
 * @return {String} The full name of the month.
 */
Date.prototype.getMonthName = function(){
	return Date.monthNames[this.getMonth()];
};

/**
 * @return {String} The short name of the month.
 */
Date.prototype.getShortMonthName = function(){
	return Date.shortMonthNames[this.getMonth()];
};

/**
 * @return {String} The single character representation of the month.
 */
Date.prototype.getMonthChar = function(){
	return Date.monthChars[this.getMonth()];
};

/**
 * Returns the normalized numeric representation of the month.
 * (i.e. January = 1, February = 2, ..., December = 12)
 * @return {Number} The normalized month number (January = 1)
 */
Date.prototype.getMonthNumber = function(){
	return this.getMonth() + 1;
};

/**
 * Returns the number of days in the current month and year, adjusting February
 * for leap years.
  * @return {Number} The number of days in the month. 
 */
Date.prototype.daysInMonth = function(){
	return Date.daysInMonth(this.getMonth(), this.getFullYear());
}

/**
 * Adds the given number of days to the date. To subtract days, pass in a negative value for offset.
 * @param {Number} offset The number of days (positive or negative) to adjust the date.
 * @return {Date} Returns the date object itself, with the additional days added.
 */
Date.prototype.addDays = function(offset){
	this.setDate(this.getDate() + offset);
	return this;
};

/**
 * Adds the given number of months to the date.  To subtract
 * months, pass in a negative value for offset.
 * @param {Number} offset The number of months (positive or negative) to adjust the date.
 * @return {Date} Returns the date object itself, with the additional months added.
 */
Date.prototype.addMonths = function(offset){
	this.setMonth(this.getMonth() + offset);
	return this;
};

/**
 * Adds the given number of years to the date.  To subtract 
 * years, pass in a negative value for offset.
 * @param {Number} offset The number of years (positive or negative) to adjust the date.
 * @return {Date} Returns the date object itself, with the additional years added.
 */
Date.prototype.addYears = function(offset){
	this.setFullYear(this.getFullYear() + offset);
	return this;
};

/**
 * Adds the given number of hours to the time portion of a date.  
 * To subtract time, pass in a negative value for offset.
 * @param {Number} offset The number of hours (positive or negative) to adjust the time.
 * @return {Date} Returns the date object itself, with the additional hours added.
 */
Date.prototype.addHours = function(offset){
	this.setHours(this.getHours() + offset);
	return this;
};

/**
 * Adds the given number of minutes to the time portion of a date.  
 * To subtract time, pass in a negative value for offset.
 * @param {Number} offset The number of minutes (positive or negative) to adjust the time.
 * @return {Date} Returns the date object itself, with the additional minutes added.
 */
Date.prototype.addMinutes = function(offset){
	this.setMinutes(this.getMinutes() + offset);
	return this;
};

/**
 * Adds the given number of seconds to the time portion of a date.  
 * To subtract time, pass in a negative value for offset.
 * @param {Number} offset The number of seconds (positive or negative) to adjust the time.
 * @return {Date} Returns the date object itself, with the additional seconds added.
 */
Date.prototype.addSeconds = function(offset){
	this.setSeconds(this.getSeconds() + offset);
	return this;
};

/**
 * @return {Date} Returns the date object itself, with the time cleared.
 */
Date.prototype.clearTime = function(){
  this.setHours(0); 
  this.setMinutes(0);
  this.setSeconds(0); 
  this.setMilliseconds(0);
  return this;
};

/** 
 * Date comparison functions.  If ignoreTime is true, then the time portion will
 * be ignored during the comparison.
 *
 * For a comparison such as myDate.compareTo(otherDate), read it
 * as "How does myDate compare to otherDate?"
 *
 *   if myDate is greater than otherDate, return > 0
 *   if myDate is less than otherDate, return < 0
 *   if myDate is equal to otherDate, return 0
 *   
 * @param {Date} date The date to compare to.
 * @param {Boolean} ignoreTime If true, ignores the time component of the date.
 * @return {Number} Returns > 0 if greater than date, < 0 if less than date, or 0 if equal to date. 
 */
Date.prototype.compareTo = function(date, ignoreTime){
	if (date == null) {
		return false;
	}
	if (ignoreTime) {
		var d1 = this.copy().clearTime();
		var d2 = date.copy().clearTime();
		return (d1.getTime() - d2.getTime());
	}
	return (this.getTime() - date.getTime());
}

/**
 * Returns true if this date is before another date.
 * @param {Date} date The date to compare to.
 * @param {Boolean} ignoreTime If true, ignores the time component of the date.
 * @return {Boolean} True if this date is before the other date, false otherwise.
 */
Date.prototype.isBefore = function(date, ignoreTime){
	return this.compareTo(date, ignoreTime) < 0;
};

/**
 * Returns true if this date is after another date.
 * @param {Date} date The date to compare to.
 * @param {Boolean} ignoreTime If true, ignores the time component of the date.
 * @return {Boolean} True if this date is after the other date, false otherwise.
 */
Date.prototype.isAfter = function(date, ignoreTime){
	return this.compareTo(date, ignoreTime) > 0;
};

/**
 * Returns true if this date is equal to another date.
 * @param {Date} date The date to compare to.
 * @param {Boolean} ignoreTime If true, ignores the time component of the date.
 * @return {Boolean} True if this date is equal to the other date, false otherwise.
 */
Date.prototype.equals = function(date, ignoreTime){
	return this.compareTo(date, ignoreTime) == 0;
};

/**
 * Return a date in the given format.  Use ^ to force the use of a literal character.
 *
 * Field		| Full Form	  | Short Form
 * -------------+----------------+-------------------
 * Year		 	| Y (4 digits)   | y (2 digits)
 * Month		| M (2 digits)   | m (1 or 2 digits)
 * Month Name   | N (full name)  | n (abbr)
 * Day of Month | D (2 digits)   | d (1 or 2 digits)
 * Day Name	 	| W (full name)  | w (abbr)
 * Hour (1-12)  | H (2 digits)   | h (1 or 2 digits)
 * Hour (0-23)  | R (2 digits)   | r (1 or 2 digits)
 * Minute	   	| I (2 digits)   | i (1 or 2 digits)
 * Second	   	| S (2 digits)   | s (1 or 2 digits)
 * AM/PM		| A (upper case) | a (lower case)
 * 
 * @param {String} format The format in which the date is to be returned.
 * @return {String} The formatted date.
 */
Date.prototype.toFormat = function(format){
	var pad = function(val) { 
		return (val > 9 ? '' : '0')+val; 
	};
	var result = '';
	for(var i = 0, len = format.length; i < len; i++) {
		var c = format.charAt(i);
		switch(c) {
			case 'Y': 
				result += this.getFullYear();
				break;
			case 'y': 
				result += (this.getFullYear()+'').substr(2,2);
				break;
			case 'M': 
				result += pad(this.getMonth()+1);
				break;
			case 'm': 
				result += this.getMonth()+1;
				break;
			case 'N':
				result += Date.monthNames[this.getMonth()];
				break;
			case 'n': 
				result += Date.shortMonthNames[this.getMonth()];
				break;
			case 'D': 
				result += pad(this.getDate());
				break;
			case 'd': 
				result += this.getDate();
				break;
			case 'W': 
				result += Date.dayNames[this.getDay()];
				break;
			case 'w': 
				result += Date.shortDayNames[this.getDay()];
				break;
			case 'H': 
				var hour = this.getHours() % 12;
				result += pad((hour ? hour : 12));
				break;
			case 'h': 
				var hour = this.getHours() % 12;
				result += (hour ? hour : 12);
				break;
			case 'R': 
				result += pad(this.getHours());
				break;
			case 'r': 
				result += this.getHours();
				break;
			case 'I': 
				result += pad(this.getMinutes());
				break;
			case 'i': 
				result += this.getMinutes().toString();
				break;
			case 'S': 
				result += pad(this.getSeconds());
				break;
			case 's':
				result += this.getSeconds().toString();
				break;
			case 'A': 
				result += (this.getHours() < 12 ? 'AM' : 'PM');
				break;
			case 'a': 
				result += (this.getHours() < 12 ? 'am' : 'pm');
				break;
			default:
				result += (c == '^' ? format.charAt(++i) : c);
		}
	}
	return result;
};


/**
 * Creates an object literal populated with date instance members.
 * @return {Object} Object literal with named properties for the date instance.
 */
Date.prototype.toObject = function(){
	return {
		month: 			this.getMonth()+1,
		monthName: 		Date.monthNames[this.getMonth()],
		day: 			this.getDate(),
		dayOfWeek: 		Date.dayNames[this.getDay()],
		year: 			this.getFullYear(),
		time:			this.toLocaleTimeString(),
		hours: 			this.getHours(),
		minutes: 		this.getMinutes(),
		seconds: 		this.getSeconds(),
		milliseconds: 	this.getMilliseconds(),
		offset: 		this.getTimezoneOffset(),
		value: 			this.getTime(),
		UTC: 			this.toUTCString()
	}
};

/**
 * Attempts to convert a string into a date based on a given format.  Fields will
 * match either the long or short form, except in the case of the year, where the 
 * string must match either a 2-digit or 4-digit format.  Ranges are checked.  Day
 * names are expected if they are included in the format string, but are otherwise
 * ignored.  Use ^ to force the use of a literal character. In other words, to
 * have the character Y appear insead of the actual year, use ^Y.
 * See formatting options in the comment for Date.prototype.toFormat
 * @param {String} format The basic format of the string.
 * @return {Date} The string as a date object.
 */
String.prototype.toDate = function(format){
	// Default values set to midnight Jan 1 of the current year.
	var year = new Date().getFullYear();
	var month = 0;
	var day = 1;
	var hours = 0;
	var minutes = 0;
	var seconds = 0;

	// Positions of each date element within the source string.  Use to know 
	// which backreference to check after a successful match.
	var yearPos = -1;
	var monthPos = -1;
	var dayPos = -1;
	var hoursPos = -1;
	var minutesPos = -1;
	var secondsPos = -1;
	var amPmPos = -1;

	var monthStyle = 'm';	   // How we interpret the month, digits (M/m) or names (N/n)
	var hoursStyle = 'h';	   // How we interpret the hours, 12-hour (h) or 24-hour (r)

	var position = 1;		   // Position of the current date element (year, month, day, etc.) in the source string
	var pattern = '';		   // Date pattern to be matched.

	// Remove extraneous whitespace from source string and format string.
	var str = this.replace(/\s+/g, ' ');
	format = format.replace(/\s+/g, ' ');

	// Loop throught the format string, and build the regex pattern
	// for extracting the date elements.
	for (var i = 0, len = format.length; i < len; i++) {
		var c = format.charAt(i)
		switch (c) {
			case 'Y' :
				pattern += '(\\d{4})';
				yearPos = position++;
				break;
			case 'y' :
				pattern += '(\\d{2})';
				yearPos = position++;
				break;
			case 'M' :
			case 'm' :
				pattern += '(\\d{1,2})';
				monthPos = position++;
				monthStyle = 'm'
				break;
			case 'N' :
				pattern += '(' + Date.monthNames.join('|') + ')';
				monthPos = position++;
				monthStyle = 'N';
				break;
			case 'n' :
				pattern += '(' + Date.shortMonthNames.join('|') + ')';
				monthPos = position++;
				monthStyle = 'n';
				break;
			case 'D' :
			case 'd' :
				pattern += '(\\d{1,2})';
				dayPos = position++;
				break;
			case 'W' : // We'll match W, but won't do anything with it.
				pattern += '(' + Date.dayNames.join('|') + ')';
				position++;
				break;
			case 'w' : // We'll match w, but won't do anything with it.
				pattern += '(' + Date.shortDayNames.join('|') + ')';
				position++;
				break;
			case 'H' :
			case 'h' :
				pattern += '(\\d{1,2})';
				hoursPos = position++;
				hoursStyle = 'h';
				break;
			case 'R' :
			case 'r' :
				pattern += '(\\d{1,2})';
				hoursPos = position++;
				hoursStyle = 'r';
				break;
			case 'I' :
			case 'i' :
				pattern += '(\\d{1,2})';
				minutesPos = position++;
				break;
			case 'S' :
			case 's' :
				pattern += '(\\d{1,2})';
				secondsPos = position++;
				break;
			case 'A' :
			case 'a' :
				pattern += '(AM|am|PM|pm)';
				amPmPos = position++;
				break;
			default :
				pattern += (c == '^' ? format.charAt(++i) : c);
		}
	}
	
	// Pull out the date elements from the input string
	var matches = str.match(new RegExp(pattern));
	if (!matches) {
		return null;	
	}
	// Now we have to interpret each of those parts...
	if (yearPos > -1) {
		year = parseInt(matches[yearPos], 10);
		year = (year < 50 ? year + 2000 : (year < 100 ? year + 1900 : year));
	}
	
	if (monthPos > -1) {
		switch (monthStyle) {
			case 'm':
				month = parseInt(matches[monthPos], 10) - 1;	// JavaScript months are zero based, user input generally is not.
				if (month > 11)
					return null;
				break;
			case 'N': 
				month = parseInt(Date.monthNumbers[matches[monthPos]], 10);
				if (isNaN(month))
					return null;
				break;
			case 'n':
				month = parseInt(Date.shortMonthNumbers[matches[monthPos]], 10);
				if (isNaN(month))
					return null;
				break;
		}
	}
	
	if (dayPos > -1) {
		day = parseInt(matches[dayPos], 10);
		if ((day < 1) || (day > Date.daysInMonth(month, year)))
			return null;
	}
	
	if (hoursPos > -1) {
		hours = parseInt(matches[hoursPos], 10);
		if (hoursStyle == 'h' && (hours == 0 || hours > 12))
			return null;
		else if (hours > 23)
			return null;
	}
	
	if (minutesPos > -1) {
		minutes = parseInt(matches[minutesPos], 10);
		if (minutes > 59)
			return null;
	}
	
	if (secondsPos > -1) {
		seconds = parseInt(matches[secondsPos], 10);
		if (seconds > 59)
			return null;
	}
	
	// Convert 12-hour time, if used, to 24-hour time.
	if (amPmPos > -1) {
		var amPm = matches[amPmPos];
		if ((amPm == 'pm' || amPm == 'PM') && (hours < 12))
			hours += 12;
	}
	
	return new Date(year, month, day, hours, minutes, seconds);
}


/**
 * Adds support for prototype's Range object.
 */
Date.prototype.succ = function(){
		var ret = new Date(this.getTime());
		ret.setDate(this.getDate() + 1);
		return ret;
};
/**
 * Month Range is a range of dates representing one month.
 */
var MonthRange = Class.create(ObjectRange, {
	initialize : function(year, month){
		this.date = this.buildDate(year, month);
		this.start = this.buildStart(this.date);
		this.end = this.buildEnd(this.date);
	},
	buildDate : function(year, month){
		var d = false;
		if(year instanceof Date){
			d = year;
		}
		else{
			d = new Date();
			d.setYear(year);
			d.setMonth(month);
		}							
							
		d.setDate(1);
		d.setHours(0);
		d.setMinutes(0);
		d.setSeconds(0);
		d.setMilliseconds(0);
		
		return d;
	},
	
	buildStart : function(date){
		return new Date(date.getTime());
	},

	buildEnd : function(date){
		var maxDays = Date.daysInMonth(date.getMonth(), date.getFullYear());		
		this.setMaxDays(maxDays);
		return new Date(date.getFullYear(), date.getMonth(), maxDays);
	},
	
	setMaxDays : function(num){
		this.maxDays = num;
	},
	
	getMaxDays : function(){
		return this.maxDays || 0;
	},
	
	getNextMonth : function(){
		return this.buildDate(this.date.getFullYear(), this.date.getMonth()+1);
	},
	
	getDate : function(){
		return this.date;
	},
	
	getPreviousMonth : function(){
		return this.buildDate(this.date.getFullYear(), this.date.getMonth()-1);
	},
	
	succ : function(){
		return new MonthRange(this.getNextMonth());
	}
});

/* end date-extensions.js */


/* start yacal.js */
/**
 * Yet Another Calendar (YaCal)
 * A mini calendar for selecting an individual date.
 * @author pop-wad [tw]
 * TO IMPROVE:
 * 	- allow data population (monthDTO, JSON)
 */
var YaCal = Class.create();
// Static members
Object.extend(YaCal, {
	// Utility to convert a string to a date object and purge time information. Format is optional.
	parseDate: function(dt, format){
		// null needs to pass through untampered
		if(Object.isString(dt)){
			dt = (Object.isString(format)) ? dt.toDate(format) : new Date(dt);
		}
		if(dt instanceof Date){
			dt.clearTime();
		}
		return dt;
	}
});

// Instance members
Object.extend(YaCal.prototype, {
	_selectedDate: null,
	_availableDates: false,
	_minDate: null,
	_maxDate: null,
	
	initialize: function(container, options){
		this.options = Object.extend({
			initialDate: new Date(),
			titleFormat: false,
			footFormat: false
		}, options || {});
		
		if($(container)){
			this.container = $(container);
			this.containerID = this.container.identify();
		} else{return;}
		
		this.insertTable();
		this.table.observe('click', this.__tableClick.bindAsEventListener(this));
		this.today = new Date().clearTime();
		var dtToShow = new Date(this.today.getTime());
		
		// Check for setting available dates instead of minDate and maxDate options
		if(this.options.availableDates){
			this._availableDates = [];
			this.options.availableDates.each(function(date){this.addAvailableDates(date);}, this);
			this._availableDates.sort();
			var min = new Date();
			min.setTime(this._availableDates.first());
			this.options.minDate = min;
			var max = new Date();
			max.setTime(this._availableDates.last())
			this.options.maxDate = max;
		}		
	
		// check for minDate and maxDate options
		if(this.options.minDate){
			this.setMinDate(YaCal.parseDate(this.options.minDate));
		}
		if(this.options.maxDate){
			this.setMaxDate(YaCal.parseDate(this.options.maxDate));
		}
		
		this.options.initialDate = YaCal.parseDate(this.options.initialDate);
		dtToShow = new Date(this.options.initialDate.getTime());
		
		if(this.options.initialSelectedDate){
			var dtInitialSelected = YaCal.parseDate(this.options.initialSelectedDate);
			this.setSelectedDate(dtInitialSelected);
			dtToShow = new Date(dtInitialSelected.getTime());
		}		

		this.bindTable(dtToShow);
	},
	
	// Creates the table markup and inserts into the container.
	insertTable: function(){
		this.table = new Element('table', {'class': 'yacal', 'cellspacing':'0'});
		if(this.options.summary){
			this.table.writeAttribute('summary', this.options.summary);
		}
		if(this.options.caption){
			this.table.insert(new Element('caption').update(this.options.caption));
		}

		this.thead = new Element('thead');
		
		this.thead.insert(this.buildNavRow());
		this.thead.insert(this.buildDOWRow());
		this.table.insert(this.thead);
		this.tbody = new Element('tbody');
		
		this.table.insert(this.tbody);
		
		this.tfoot = new Element('tfoot');
		this.tfoot.insert(this.buildFootRow());
		this.table.insert(this.tfoot);
		
		this.container.insert(this.table);
		
	},
	
	// Binds the month table for the given date and adjusts navigation.
	bindTable: function(date){
		this.range = this.buildMonthRange(date);
		this.monthDTO = this.buildMonthDTO(this.range);
		var title = (this.options.titleFormat != false) ? 
				this.range.date.toFormat(this.options.titleFormat) : 
				this.range.date.getMonthName() + " " + this.range.date.getFullYear();
		
		this.thead.down('th.title').update(title);
		// add/remove disabled on next previous
		if(this._maxDate != null){
			var tdNext = this.thead.down('td.next');
			var nextMonth = this.getNextMonth(this.range.date);
			var maxMonth = new Date(this._maxDate.getTime());
			maxMonth.setDate(1);
			(nextMonth > maxMonth) ? tdNext.addClassName('disabled') : tdNext.removeClassName('disabled');
		}
		
		if(this._minDate != null){
			var tdPrevious = this.thead.down('td.previous');
			var previousMonth = this.getPreviousMonth(this.range.date);
			var minMonth = new Date(this._minDate.getTime());
			minMonth.setDate(1);
			(previousMonth < minMonth ) ? tdPrevious.addClassName('disabled') : tdPrevious.removeClassName('disabled');
		}
		
		this.tbody.update(); // clear out old day cells
		for(i = 0, len = this.monthDTO.size(); i < len; i++){
			this.tbody.insert(this.buildWeekRow(this.monthDTO[i]));
		}

	},
	
	rebindTable: function(){
		this.bindTable(new Date(this.range.date.getTime()));
	},
	
	// Creates elements for the month navigation row.
	buildNavRow: function(){
		/*
		<tr class="nav">
			<td class="previous"><a href="#previous">&#60;</a></td>
			<th class="title" scope="row" colspan="5">March 2010</th>
			<td class="next"><a href="#next">&#62;</a></td>
		</tr>
		*/
		var navRow = new Element('tr', {'class': 'nav'});
		navRow.insert(new Element('td', {'class': 'previous'}).update('<a href="#previous">Previous</a>'));
		navRow.insert(new Element('th', {'class': 'title', 'colspan': '5'}));
		navRow.insert(new Element('td', {'class': 'next'}).update('<a href="#next">Next</a>'));
		return navRow;
	},
	// Builds a range of months representing a month
	buildMonthRange: function(year, month){	
		return new MonthRange(year, month);
	},
	// Adds data to the month range (to be refined for passing date specific data)
	buildMonthDTO: function(range){
		var monthArr = [];
		var week = [];								

		// populate offset with undefined
		// I may need to find a better way for this. inGroupsOf()?
		range.start.getDay().times(function(itr){ week.push(undefined) });

		range.each(function(date){
			 week.push(date);
			 if(date.getDay() == 6){ // end of week
				monthArr.push(week);
				week = [];
			}
		}.bind(this));
		
		if(week.length > 0){
			while(week.length < 7){
				week.push(undefined);
			}
			monthArr.push(week);
		}
		return monthArr;
	
	},
	// Creates a weeks worth of cells and applies appropriate css classes.
	buildWeekRow: function(weekdays){
		var weekRow = new Element('tr');
		// classes: selected weekend today disabled other
		weekdays.each(function(day, index){
			var td = new Element('td');
			// weekend?
			if(index == 0 || index == 6){td.addClassName('weekend');}
			
			if(!Object.isUndefined(day)){
				var el;
				if(day.equals(this.today, true)){
					td.addClassName('today');
				}
				if(this._selectedDate && (day.equals(this._selectedDate, true))){
					td.addClassName('selected');
				}
				// date is between optional min and max dates, and is a member of optional available dates
				
				if(((this._minDate == null) ? true : (day >= this._minDate)) 
					&& ((this._maxDate == null) ? true : (day <= this._maxDate))
					&& ((this._availableDates == false) ? true : (this._availableDates.member(day.getTime())))
				){
					el = new Element('a', {'href': '#' + day.toDateString()}).update(day.getDate());
				}else{
					el = new Element('span').update(day.getDate());
					td.addClassName('disabled');
				}
				
				td._day = day; // store date obj as private member.
				
				td.update(el);
			}
			else{
				td.addClassName('other');
			}
			weekRow.insert(td);
		}.bind(this));
		
		return weekRow;
	},
	// Creates day of week header row.
	buildDOWRow: function(){
		/*
			<tr class="dow">
				<th scope="col" class="weekend"><abbr title="Sunday">S</abbr></th>
				<th scope="col"><abbr title="Monday">M</abbr></th>
				<th scope="col"><abbr title="Tuesday">T</abbr></th>
				<th scope="col"><abbr title="Wednesday">W</abbr></th>
				<th scope="col"><abbr title="Thursday">T</abbr></th>
				<th scope="col"><abbr title="Friday">F</abbr></th>
				<th scope="col" class="weekend"><abbr title="Saturday">S</abbr></th>
			</tr>
		*/
		var dowRow = new Element('tr', {'class': 'dow'});
		for(var i=0, len = Date.dayNames.size(); i < len; i++){
			var abbr = new Element('abbr', {title: Date.dayNames[i]}).update(Date.dayChars[i]);
			var th = new Element('th', {'scope': 'col'}).update(abbr);
			
			th.insert(abbr);
			if(i == 0 || i == 6){
				th.addClassName('weekend');
			}
			dowRow.insert(th);
		}
		return dowRow;
	},
	// Creates tfoot section with a cell for showing the selected date.
	buildFootRow: function(){
		/*
		<tfoot>
			<tr>
				<td class="selected" colspan="7">MM DD, YYYY</td>
			</tr>
		</tfoot>
		*/
		var footRow = new Element('tr');
		footRow.insert(new Element('td', {'class':'selected', 'colspan': '7'}));
		return footRow;
	},
	// Clears selected date and rebuilds the calendar with the initial date.
	reset: function(){
		this.setSelectedDate(null);
		this.bindTable(this.options.initialDate);
	},
	//Selects the date and optionally forces showing of the selected month.
	setSelectedDate: function(dt, bShowSelectedMonth){
		dt = YaCal.parseDate(dt);
		
		this._selectedDate = dt;

		var sDate = '';
		if(dt != null){
			sDate = (this.options.footFormat != false) ? dt.toFormat(this.options.footFormat): dt.toDateString() ;	
		}
		
		this.tbody.select('td').each(function(td){
			if((td._day) && (dt) && td._day.toDateString() == dt.toDateString()){
				td.addClassName('selected')
			}
			else{
				td.removeClassName('selected');
			}
		});
		this.tfoot.down('tr td.selected').update(sDate);
		// optional
		if(bShowSelectedMonth && bShowSelectedMonth == true){
			this.showSelectedMonth();
		}
	},
	// Gets the calendar's selected date, returns false if no date has been selected.
	getSelectedDate: function(){
		var dtSelected = false;
		if(this._selectedDate != null){
			dtSelected = new Date(this._selectedDate.getTime());// protect reference
		}
		return dtSelected;
	},
	// Gets the calendar's selected date, returns false if no date has been selected.
	toString: function(){
		return this.getSelectedDate();
	},	
	// Gets the currently showing month.
	getMonthShowing: function(){
		return new Date(this.range.date.getTime());
	},
	// Event handler for a click anywhere within the table; Dispatches next month, previous month, day selections.
	__tableClick: function(e){
		// examine e.element() and perform relevant action if any.
		e.stop();
		var element = e.element();
		var tdNext = this.thead.down('tr.nav td.next');
		var tdPrevious = this.thead.down('tr.nav td.previous');
		if(element.descendantOf(tdPrevious)){
			if(!tdPrevious.hasClassName('disabled')){
				this.showPreviousMonth();
			}
		}
		else if(element.descendantOf(tdNext)){
			if(!tdNext.hasClassName('disabled')){
				this.showNextMonth();
			}
		}
		else if(element.descendantOf(this.tbody) && element.nodeName == 'A'){
			var tdDay = element.up('td');
			this.setSelectedDate(tdDay._day);
			
			this._daySelected(this._selectedDate);
		}
	},
	// Shows the previous month.
	showPreviousMonth: function(){
		var date = new Date(this.range.start.getTime());
		date = this.getPreviousMonth(date);
		this.bindTable(date);
		this._monthSelected(date);
	},
	// Shows the next month
	showNextMonth: function(){
		var date = this.range.end.copy();//new Date(this.range.end.getTime());
		date = this.getNextMonth(date);
		
		this.bindTable(date);
		this._monthSelected(date);
	},
	// Shows the month of the given date.
	showMonth: function(dt){
		dt = YaCal.parseDate(dt);
		dt.setDate(1);
		
		this.bindTable(dt);
	},
	// Shows the NOW month.
	showCurrentMonth: function(){
		this.showMonth(new Date());
	},
	// Shows the month that contains the selected date.
	showSelectedMonth: function(){
		var dtSelected = this.getSelectedDate();
		if(dtSelected !== false){
			var isShowing = (
				(this.range.date.getMonth() == dtSelected.getMonth())
				&&
				(this.range.date.getFullYear() == dtSelected.getFullYear())
			);
			if(!isShowing){
				this.showMonth(dtSelected);
			}
		}
	},
	// Broadcasts a day selected event
	_daySelected: function(dt){
		this.container.fire(this.containerID + ':day_selected', {"date": dt});
	},
	// Broadcasts a month selected event
	_monthSelected: function(dt){
		this.container.fire(this.containerID + ':month_selected', {"date":dt});
	},
	// Internal utility function to get a previous month date time.
	getPreviousMonth: function(dt){
		var date = dt.copy().clearTime();
		date.setDate(1);
		date.addMonths(-1);
		return date;
	},
	// Internal utility function to get a next month date time.
	getNextMonth: function(dt){
		var date = dt.copy().clearTime();
		date.setDate(1);
		date.addMonths(1);
		return date;
	},
	// Sets a new minimum date to be available for selection.
	setMinDate: function(dt){
		this._minDate = YaCal.parseDate(dt);
	},
	// Sets a new maximum date to be available for selection.
	setMaxDate: function(dt){
		this._maxDate = YaCal.parseDate(dt);
	},
	// Makes dates available. dt can be a date range, a single date, or a date string
	addAvailableDates: function(dt){
		if(dt instanceof(ObjectRange)){
			var dates = $A(dt);
			for(var i=0, len = dates.size(); i < len ; i++){
				dates[i].clearTime();
				this._addAvailableDate(dates[i]);
			}
		}
		else{
			dt = YaCal.parseDate(dt);
			this._addAvailableDate(dt);
		}
		this._availableDates.sort();
	},
	// Private utility method to add a new date.
	_addAvailableDate: function(dt){
		var timestamp = dt.getTime();
		if(!(this._availableDates.member(timestamp))){
			this._availableDates.push(timestamp);
		}
	},
	//Gets an array of all available dates, if being used.
	getAvailableDates: function(){
		return this._availableDates.collect(function(timestamp){
			var dt = new Date();
			dt.setTime(timestamp);
			return dt;
		});
	}
});
/* end yacal.js */


/* start date-input.js */
/**
 * DateInput
 * Mini calendar (YaCal) extended with open/close methods.
 * @author pop-wad [tw]
 */
var DateInput = Class.create(YaCal, {
	initialize: function($super, element, options){
		this.element = $(element); // text input element
		options = options || {};
		this.valueFormat = options.valueFormat || false;
		if(this.element.present() && Object.isUndefined(options.initialSelectedDate)){
			options.initialSelectedDate = YaCal.parseDate(this.element.value, this.valueFormat);
		}
		// construct wrapper and content html
		this.wrapper = new Element('div', {'class': 'dateinput', 'id': this.element.identify() + '-dateinput'});
		this.wrapper.update(options.contentSkin || "<div id='" + this.element.identify() + "-dateinput-container' class='container'></div>");
		element.insert({after: this.wrapper});
		
		var container = this.wrapper.down("div.container");
		if(!(Object.isElement(container))){
			throw (new Error("DateInput content skin does not have a div.container for the calendar table."));
		}
		// look for optional close link 
		var lnkClose = this.wrapper.down('a.close');
		if(Object.isElement(lnkClose)){
			lnkClose.observe('click', this.__lnkCloseClick.bindAsEventListener(this));
		}
		
		// pull effects out
		this._useEffects = options.useEffects || true;
		
		if (this._useEffects) {
			// pull effects out of the options and into private
			this._effects = Object.extend({
					openEffectType: Effect.SlideDown,
					openEffectOptions: {
						duration: 0.25,
						queue: {
							position: 'end',
							scope: 'dateinputscope'
						}
					},
					
					closeEffectType: Effect.SlideUp,
					closeEffectOptions: {
						duration: 0.25,
						queue: {
							position: 'end',
							scope: 'dateinputscope'
						}
					}
				}, options.effects || {});
			
			delete options.effects;
		}
		
		// initialize base YaCal
		$super(container, options);
		
		// event listeners		
		this.element.observe('change', this.__inputChange.bindAsEventListener(this)); 
		this.element.observe('focus', this.__inputFocus.bindAsEventListener(this));
		Event.observe(document.body, 'click', this.__bodyClick.bindAsEventListener(this));
		document.observe(this.containerID + ':day_selected', this.__daySelected.bindAsEventListener(this));
		// optional external trigger (calendar icon)
		this.trigger = (Object.isElement(options.trigger)) ? $(options.trigger) : false;
		if(this.trigger){
			this.trigger.observe('click', this.__triggerClick.bindAsEventListener(this));
		}

		this.wrapper.hide();
	},
	// Closes calendar if clicked outside.
	__bodyClick: function(e){
		var el = e.element();
		if(!el.descendantOf(this.container) && el !== this.element){
			this.close();
		}
	},
	// Closes calendar
	__lnkCloseClick: function(e){
		e.stop();
		this.close();
	},
	// Trigger (calendar icon)
	__triggerClick: function(e){
		e.stop();
		(this.wrapper.visible() == true) ? this.close() : this.open();
	},
	// Synchronizes the input value and yacal's selected date.
	__inputChange: function(e){
		if(this.element.present()){
			var dtSelected = this.getSelectedDate();
			var dtChanged = YaCal.parseDate(this.element.value, this.valueFormat);
			// basic validation
			if(dtChanged instanceof Date && !(isNaN(dtChanged.getTime()))){
				//console.log("valid date");
				if(dtSelected != dtChanged){
					this.setSelectedDate(dtChanged, true);	
				}
				this.close();
			}else{
				//console.warn('invalid date');
				if(dtSelected instanceof Date){
					//console.log("resetting input to the previously selected date")
					this.setValue(dtSelected);
				}
				this.element.fire(this.element.id + ':invalid_date', {"DateInput": this});
			}
		} else {
			this.reset();
		}
	},
	// Handles focus event on text input
	__inputFocus: function(e){
		this.open();
	},
	
	// Handles a date selected on Yacal, sets the text input's value
	__daySelected: function(ce){
		this.setValue(ce.memo.date);
		this.close();
	},
	// Opens the calendar
	open: function(){
		if (this.isOpen() == false && this._useEffects == true) {
			new this._effects.openEffectType(this.wrapper, this._effects.openEffectOptions);
		}else{
			this.wrapper.show();
		}
	},
	// Closes the calendar
	close: function(){
		if(this.isOpen() == true && this._useEffects == true){
			new this._effects.closeEffectType(this.wrapper, this._effects.closeEffectOptions);
		} else{
			this.wrapper.hide();
		}
	},
	// Sets the value of the text input.
	setValue: function(dt){
		var sFormat = this.valueFormat || 'm/d/Y'; 
		this.element.value = dt.toFormat(sFormat);
	},
	// Checks the visiblity of the wrapper.
	isOpen: function(){
		return this.wrapper.visible();
	}
});
/* end date-input.js */

/* start StarEndDates */
/* Forza specific for start + end date-pickers used on scoreboard filter */
var StartEndDates =  Class.create({
	initialize: function(startInputID, endInputID, options){
		
		this.txtStart = $(startInputID);
		this.txtEnd = $(endInputID);
		this.options = Object.extend({
			minDate: new Date('10/1/2009'),
			maxDate: new Date(),
			valueFormat: LocalizationInfo.jsDateFormat || 'm/d/y' // getting default from global localization object.
		}, options || {});
		if(!(Object.isElement(this.txtStart)) || !(Object.isElement(this.txtEnd))){return;}
		
		this.startDateInput = new DateInput(this.txtStart, this.options);
		this.endDateInput = new DateInput(this.txtEnd, this.options);		
		
		document.observe(this.startDateInput.containerID + ':day_selected', this.__daySelected.bind(this));
		document.observe(this.endDateInput.containerID + ':day_selected', this.__daySelected.bind(this));
	},
	
	__daySelected: function(ce){
		var dateInputID = ce.target.identify();
		var dtStart = this.startDateInput.getSelectedDate();
		var dtEnd = this.endDateInput.getSelectedDate();
				
		switch(dateInputID){
			case this.startDateInput.containerID:
				if(dtEnd !== false && dtStart > dtEnd){
					this.yacalEnd.setSelectedDate(dtStart, true);
				}

				dtStart.setDate(dtStart.getDate());
				this.endDateInput.setMinDate(dtStart);
				this.endDateInput.rebindTable();
			
				break;
			case this.endDateInput.containerID:
				if(dtStart !== false && dtEnd < dtStart){
					this.yacalStart.setSelectedDate(dtEnd, true);
				}
				dtEnd.setDate(dtEnd.getDate());
				this.startDateInput.setMaxDate(dtEnd);
				this.startDateInput.rebindTable();

				break;
		}
		
	}
	
});
/* end StarEndDates */
