/*
	Filename		: calendar.js
	Project			: Popup Calendar
	Copyright		: 2001, 2002 Richard Heyes
*/


//	Global variables

	dynCalendar_layers			= new Array();
	dynCalendar_mouseoverStatus	= false;
	dynCalendar_mouseX			= 0;
	dynCalendar_mouseY			= 0;


/*	The calendar constructor

	string objName		Name of the object that you create
	string callbackFunc	Name of the callback function
	string OPTIONAL		Optional layer name
	string OPTIONAL		Optional images path
*/

	function dynCalendar(objName, callbackFunc)
		{
		//	Properties
		//	Todays date
		this.today				= new Date();
		this.date				= this.today.getDate();
		this.month				= this.today.getMonth();
		this.year				= this.today.getFullYear();

		this.objName			= objName;
		this.callbackFunc		= callbackFunc;
		this.imagesPath			= arguments[2]?arguments[2]:'/img';
		this.layerID			= arguments[3]?arguments[3]:'dynCalendar_layer_'+dynCalendar_layers.length;

		this.offsetX			= 5;
		this.offsetY			= 5;

		this.useMonthCombo		= true;
		this.useYearCombo		= true;
		this.yearComboRange		= 5;

		this.currentMonth		= this.month;
		this.currentYear		= this.year;

		//	Public Methods
		this.show				= dynCalendar_show;
		this.writeHTML			= dynCalendar_writeHTML;
		this.hide				= dynCalendar_hideLayer;

		//	Accessor methods
		this.setOffset			= dynCalendar_setOffset;
		this.setOffsetX			= dynCalendar_setOffsetX;
		this.setOffsetY			= dynCalendar_setOffsetY;
		this.setImagesPath		= dynCalendar_setImagesPath;
		this.setMonthCombo		= dynCalendar_setMonthCombo;
		this.setYearCombo		= dynCalendar_setYearCombo;
		this.setCurrentMonth	= dynCalendar_setCurrentMonth;
		this.setCurrentYear		= dynCalendar_setCurrentYear;
		this.setYearComboRange	= dynCalendar_setYearComboRange;

		//	Private methods
		//	Layer manipulation
		this._getLayer			= dynCalendar_getLayer;
		this._hideLayer			= dynCalendar_hideLayer;
		this._showLayer			= dynCalendar_showLayer;
		this._setLayerPosition	= dynCalendar_setLayerPosition;
		this._setHTML			= dynCalendar_setHTML;

		//	Miscellaneous
		this._getDaysInMonth	= dynCalendar_getDaysInMonth;
		this._mouseover			= dynCalendar_mouseover;

		//	Constructor type code
		dynCalendar_layers[dynCalendar_layers.length] = this;

		this.writeHTML();
	}



/*
	Shows the calendar, or updates the layer if already visible.

	integer month Optional month number (0-11)
	integer year  Optional year (YYYY format)
*/

	function dynCalendar_show()
		{
		// Variable declarations to prevent globalisation

		var month, year, monthnames, numdays, thisMonth, firstOfMonth;
		var ret, row, i, cssClass, linkHTML, previousMonth, previousYear;
		var nextMonth, nextYear, prevImgHTML, prevLinkHTML, nextImgHTML, nextLinkHTML;
		var monthComboOptions, monthCombo, yearComboOptions, yearCombo, html;

		this.currentMonth = month = (arguments[0]!=null)?arguments[0]:this.currentMonth;
		this.currentYear  = year  = (arguments[1]!=null)?arguments[1]:this.currentYear;

		monthnames	= new Array('January','February','March','April','May','June','July','August','September','October','November','December');
		numdays		= this._getDaysInMonth(month, year);

		thisMonth	= new Date(year, month, 1);
		firstOfMonth= thisMonth.getDay();


		// First few blanks up to first day

		ret = new Array(new Array());

		for (i=0; i<firstOfMonth; i++)
			{
			ret[0][ret[0].length] = '<td>&nbsp;</td>';
			}


		// Main body of calendar

		row = 0;
		i   = 1;

		while (i<=numdays)
			{
			if (ret[row].length==7)
				{
				ret[++row] = new Array();
				}

			//	Generate this cells' HTML

			cssClass = (i==this.date && month==this.month && year==this.year) ?' class="today"':'';

			linkHTML = sprintf('<a href="javascript:%s(%s,%s,%s)"><b>%s</b></a>',
								this.callbackFunc,
								i,
								Number(month) + 1,
								year,
								i++);

			ret[row][ret[row].length] = sprintf('<td%s>%s</td>',cssClass,linkHTML);
			}

		// Format the HTML

		for (i=0; i<ret.length; i++)
			{
			ret[i] = ret[i].join('\n')+'\n';
			}

		previousYear  = thisMonth.getFullYear();
		previousMonth = thisMonth.getMonth()-1;

		if (previousMonth < 0)
			{
			previousMonth = 11;
			previousYear--;
			}

		nextYear  = thisMonth.getFullYear();
		nextMonth = thisMonth.getMonth()+1;

		if (nextMonth > 11)
			{
			nextMonth = 0;
			nextYear++;
			}

		prevImgHTML  = sprintf('&nbsp;<img src="%s/prev.gif" alt="<<" border="0" />', this.imagesPath);
		prevLinkHTML = sprintf('<a href="javascript: %s.show(%s, %s)">%s</a>'		, this.objName, previousMonth, previousYear, prevImgHTML);
		nextImgHTML  = sprintf('<img src="%s/next.gif" alt=">>" border="0" />&nbsp;', this.imagesPath);
		nextLinkHTML = sprintf('<a href="javascript: %s.show(%s, %s)">%s</a>'		, this.objName, nextMonth, nextYear, nextImgHTML);

		//	Build month combo

		if (this.useMonthCombo)
			{
			monthComboOptions = '';
			for (i=0; i<12; i++)
				{
				selected = (i==thisMonth.getMonth()?'selected="selected"':'');
				monthComboOptions += sprintf('<option value="%s" %s>%s</option>', i, selected, monthnames[i]);
				}
			monthCombo = sprintf('<select name="months" onchange="%s.show(this.options[this.selectedIndex].value, %s.currentYear)">%s</select>', this.objName, this.objName, monthComboOptions);
			}
		  else
			{
			monthCombo = monthnames[thisMonth.getMonth()];
			}

		//	Build year combo

		if (this.useYearCombo)
			{
			yearComboOptions = '';
			for (i=thisMonth.getFullYear() - this.yearComboRange; i<=(thisMonth.getFullYear() + this.yearComboRange); i++)
				{
				selected = (i==thisMonth.getFullYear() ?'selected="selected"':'');
				yearComboOptions += sprintf('<option value="%s" %s>%s</option>',i,selected,i);
				}

			yearCombo = sprintf('<select style="border: 1px groove" name="years" onchange="%s.show(%s.currentMonth, this.options[this.selectedIndex].value)">%s</select>', this.objName, this.objName, yearComboOptions);
			}
		  else
			{
			yearCombo = thisMonth.getFullYear();
			}

		html = '<table>\n';
		html += sprintf('<tr class="header">\n<td>%s</td>\n<td colspan="5" align="center">%s %s</td>\n<td align="right">%s</td>\n</tr>\n', prevLinkHTML, monthCombo, yearCombo, nextLinkHTML);
		html += '<tr class="dayname">'
			 +	'<td>Su</td>'
			 +	'<td>Mo</td>'
			 +	'<td>Tu</td>'
			 +	'<td>We</td>'
			 +	'<td>Th</td>'
			 +	'<td>Fr</td>'
			 +	'<td>Sa</td></tr>\n'
			 +	'<tr class="day">\n' + ret.join('</tr>\n<tr class="day">\n') + '</tr>\n'
			 +	'</table>\n';

		this._setHTML(html);

		if (!arguments[0] && !arguments[1])
			{
			this._showLayer();
			this._setLayerPosition();
			}
		}



//	Writes HTML to document for layer

	function dynCalendar_writeHTML()
		{
		if (is_ie5up || is_nav6up || is_gecko)
			{
//			document.write(sprintf('<a href="javascript: %s.show()"><img src="%s/dynCalendar.gif" border="0" width="16" height="16" valign="bottom" /></a>'					   , this.objName, this.imagesPath));
			document.write(sprintf('<div class="dynCalendar" id="%s" onmouseover="%s._mouseover(true)" onmouseout="%s._mouseover(false)" onclick="cancelBubble(event);"></div>', this.layerID, this.objName, this.objName));
			}
		}


	function cancelBubble(e)
		{
		if (document.all)
			{window.event.cancelBubble = true;}
		  else
			{e.cancelBubble = true;}
		}



	function dynCalendar_setOffset			(Xoffset, Yoffset)	{ this.setOffsetX(Xoffset);					//	Sets the offset to the mouse position that the calendar appears at
																  this.setOffsetY(Yoffset);				}
	function dynCalendar_setOffsetX			(Xoffset)			{ this.offsetX			= Xoffset;		}	//	Sets the X offset to the mouse position that the calendar appears at
	function dynCalendar_setOffsetY			(Yoffset)			{ this.offsetY			= Yoffset;		}	//	Sets the Y offset to the mouse position that the calendar appears at
	function dynCalendar_setImagesPath		(path)				{ this.imagesPath		= path;			}	//	Sets the images path
	function dynCalendar_setMonthCombo		(useMonthCombo)		{ this.useMonthCombo	= useMonthCombo;}	//	Turns on/off the month dropdown
	function dynCalendar_setYearCombo		(useYearCombo)		{ this.useYearCombo		= useYearCombo;	}	//	Turns on/off the year dropdown
	function dynCalendar_setCurrentMonth	(month)				{ this.currentMonth		= month;		}	//	Sets the current month being displayed
	function dynCalendar_setCurrentYear		(year)				{ this.currentYear		= year;			}	//	Sets the current month being displayed
	function dynCalendar_setYearComboRange	(range)				{ this.yearComboRange	= range;		}	//	Sets the range of the year combo. Displays this number of years either side of the year being displayed



//	Returns the layer object

	function dynCalendar_getLayer()
		{
		var layerID = this.layerID;

		if (document.getElementById(layerID))
			{
			return document.getElementById(layerID);
			}
		  else
			if (document.all(layerID))
				{
				return document.all(layerID);
				}
		}



	function dynCalendar_hideLayer()  { this._getLayer().style.visibility = 'hidden' ;}		//	Hides the calendar layer
	function dynCalendar_showLayer()  { this._getLayer().style.visibility = 'visible';}		//	Shows the calendar layer



//	Sets the layers position

	function dynCalendar_setLayerPosition()
		{
//		this._getLayer().style.top  = (dynCalendar_mouseY + this.offsetY) + 'px';
//		this._getLayer().style.left = (dynCalendar_mouseX + this.offsetX) + 'px';
		this._getLayer().style.top  = (this.offsetY) + 'px';
		this._getLayer().style.left = (this.offsetX) + 'px';
		}


//	Sets the innerHTML attribute of the layer

	function dynCalendar_setHTML(html)
		{
		this._getLayer().innerHTML = html;
		}


//	Returns number of days in the supplied month

	function dynCalendar_getDaysInMonth(month, year)
		{
		monthdays = [31,28,31,30,31,30,31,31,30,31,30,31];
		if (month!=1)
			{return monthdays[month];}

		return (((year%4==0 && year%100!=0) || year%400==0)?29:28);
		}


//	onMouse(Over|Out) event handler
//	boolean status Whether the mouse is over the calendar or not

	function dynCalendar_mouseover(status)
		{
		dynCalendar_mouseoverStatus = status;
		return true;
		}


//	onMouseMove event handler

	if (!mouseMoveEventAssigned)
		{
		dynCalendar_oldOnmousemove = document.onmousemove ? document.onmousemove : new Function;

		document.onmousemove = function ()
			{
			if (arguments[0])
				{
				dynCalendar_mouseX = arguments[0].pageX;
				dynCalendar_mouseY = arguments[0].pageY;
				}
			  else
				{
				dynCalendar_mouseX = event.clientX + document.body.scrollLeft;
				dynCalendar_mouseY = event.clientY + document.body.scrollTop;
				arguments[0] = null;
				}
	
			dynCalendar_oldOnmousemove(arguments[0]);
			}

		var mouseMoveEventAssigned = true;
		}


//	Callbacks for document.onclick

	if (!clickEventAssigned)
		{
		dynCalendar_oldOnclick = document.onclick ? document.onclick : new Function;

		document.onclick = function ()
			{
			if (!dynCalendar_mouseoverStatus)
				{
				for (i=0; i<dynCalendar_layers.length; ++i)
					{
					dynCalendar_layers[i]._hideLayer();
					}
				}

			dynCalendar_oldOnclick(arguments[0]?arguments[0]:null);
			}

		var clickEventAssigned = true;
	}




//	Javascript mini implementation of sprintf()

	function sprintf(strInput)
		{
		var strOutput  = '';
		var currentArg = 1;

		for (var i=0; i<strInput.length; i++)
			{
			if (strInput.charAt(i)=='%' && i!=(strInput.length-1) && typeof(arguments[currentArg])!='undefined')
				{
				switch (strInput.charAt(++i))
					{
					case 's':
						strOutput+=arguments[currentArg];
						break;
					case '%':
						strOutput+='%';
						break;
					}
				currentArg++;
				}
			  else
				{
				strOutput += strInput.charAt(i);
				}
			}

		return strOutput;
		} 

