/*
Copyright 2004-2008, Paul Tero, Version 1.1.0
This is a popup calendar written entirely in Javascript. It puts the date into a text field
which can be hidden. The colours and fonts are controlled by the styles below. It is implemented
as a calendar object which has several methods, and there a few additional methods for
the date object as well.

The calendar can be invoked with a line like this, where "test" is the name of the calendar. You
can also pass it a default date, a function to be called when a date is clicked (or the form field
which should save the date), whether to hide the weekends, the number of days ahead to allow clicks
(if for example only dates in the future should be clicked), the number of minutes to show if you
want a time selection as well, and the text that goes before the time selection.
<a href="javascript:void new Calendar('test','20 December 2004');">show calendar</a>

Along with that, you'll need a div to show the calendar in. This must have the corresponding
name (such as "calendar_test").
<div id="calendar_test" class="calendar"></div>

The call back function is passed the calendar name and the chosen date as an object. If there
is no call back function it refreshes the page, passing in the calendar name, time (as a number)
and date. An example call back function is:
function process(name,date) {alert (name + "=>" + date.toLocaleString());}

The following styles are available to customise the calendar. It is displayed as a table with
five or seven (depending on if weekends are shown) equally wide columns:
div.calendar {visibility: hidden; position: absolute;}
div.calendar a {text-decoration: none; color: black;}
div.calendar table {background: yellow; width:300px;}
div.calendar tr.calendararrowsandmonth {}
div.calendar th {text-align: center; border: 1px solid red; background: white;}
div.calendar th.calendarmonth {}
div.calendar th.calendararrows {}
div.calendar tr.calendardaysofweek {}
div.calendar th.calendardayofweek {background: red;}
div.calendar tr.calendardaysofmonth {}
div.calendar td {text-align: center; border: 1px solid red;}
div.calendar td a {}
div.calendar td.calendarinitial {background: green;}
div.calendar td.calendarinitial a {color: white;}
div.calendar td.calendartoday {background: blue;}
div.calendar td.calendartoday a {color: white;}
div.calendar td.calendarblank {background: white;}
div.calendar td.calendarnormal {}
div.calendar td.calendarnormal a {}
div.calendar tr.calendarclose {}
div.calendar th.calendarclose {}
div.calendar tr.calendartime {}
div.calendar td.calendartime {}
*/


//Show the properties of an object
function showProperties (o) {var r = ""; for (var p in o) {r += p + ": " + o[p] + "\t";} alert (r);}

//This uses a calendar object.
function Calendar (name, initialdate, callback, noweekends, daysahead, minutes, timetext) {
	this.name = name;
	this.fullname = "calendar_" + name;
	this.displaydate = new Date(); //the date to display
	if (!minutes) {this.displaydate.setHours (0); this.displaydate.setMinutes (0);}
	this.displaydate.setSeconds (0); //clear the hour and minute
	if (this.minutes > 1) this.displaydate.setMinutes (this.displaydate.getMinutes() - (this.displaydate.getMinutes() % this.minutes));
	if (document[this.fullname]) { //the calendar has already been used before, get the old data
		this.displaydate = document[this.fullname].displaydate;
		this.initialdate = document[this.fullname].initialdate;
	}
	else {
		//If there is no initial date but the call back is a form element, then use it's value as the initial date
		if (!initialdate && callback && typeof (callback) == 'object') initialdate = callback.value;
		if (initialdate) {this.displaydate.setTime (Date.parse (initialdate));} //set to the initialisation date
		this.initialdate = new Date (this.displaydate.getTime()); //store the initial date (done this way so it's not just a reference to the same object)
	}
	this.callback = callback; //the callback function which is passed the date
	this.weekends = noweekends ? false : true; //display weekends or not
	this.daysahead = daysahead ? daysahead : 0; //the number of days ahead to allow for bookable links
	this.minutes = minutes ? minutes : 0; //if we should show a time field as well, then this is the minutes
	this.timetext = minutes ? (timetext ? timetext : 'time:') : ''; //the text to display before the time
	this.format(); //format this calendar
	document[this.fullname] = this; //save a copy of this object (which we will reference in links)
}

//Change the text of the calendar and display or hide it
function Calendar_change (text, visible) {
	if (!document.getElementById) return; //IE and Firefox only for now
	var el = document.getElementById(this.fullname); //get the element
	if (text) el.innerHTML = text; //set the text
	el.style.visibility = visible ? "visible": "hidden"; //show or hide it
}

//Get a link which calls a function in the calendar
function Calendar_getLink (callback, text) { 
	var r = "<a href=\"javascript:void document." + this.fullname + "."; //start the link
	r += callback + "\">" + text + "</a>"; //add the call back and text
	return r; //return the link
}

//Add a month to the calendar display date
function Calendar_addMonths (howmany) {this.displaydate.addMonths(howmany); this.format();}

//Get a drop down for the time
function Calendar_getTimeDropDown (name, from, to, interval, initial) {
	r = '<select name="' + this.fullname + '_' + name + '" onchange="document.'; //start the select list
	r += this.fullname + '.returnDate (0, this)">';  //finish the onchange condition
	for (var i=from; i<=to; i+= interval) r += '<option ' + (i==initial ? 'selected="yes" ' : '') + 'value="' + i + '">' + (i<10?'0':'') + i + '</option>';
	r += '</select>';
	return r;
}

//Return the name of the calendar and the date to the requested function. The default just calls
//the same page passing in the name, display time and display date. From 17/12/2008 this can
//also accept a time drop down as the second argument, in which case it sets the minute or hour.
function Calendar_returnDate (day, tdd) {
	if (day > 0) this.displaydate.setDate(day); //set the day
	if (tdd) {var val = tdd.options[tdd.selectedIndex].value; if (tdd.name.indexOf ('_hour')>0) this.displaydate.setHours (val); else this.displaydate.setMinutes (val);}
if (this.callback) {
		this.initialdate = new Date (this.displaydate.getTime()); //store the initial date and not just a reference to it
		this.format(); //redraw the calendar to get the new initial date highlighted
		var showdate = this.displaydate.toLocaleString().replace (new RegExp('[a-z]+,? ([0-9]+ [a-z]+ [0-9]+) ([0-9]+:[0-9]+).*$', 'i'), '\$1 \$2');
		if (typeof (this.callback) == 'object') this.callback.value = this.displaydate; //this is an object reference for an HTML form
		//The call back is a form element which has select lists for _day, _month, _year, etc
		else if (document.getElementById (this.callback+'_day') && document.getElementById(this.callback+'_day').form) {
			var f = document.getElementById(this.callback+'_day').form;
			var myel = f[this.callback]; if (myel) myel.value = showdate;
			myel = f[this.callback+'_day']; if (myel) myel.selectedIndex = this.displaydate.getDate() + (myel.options[0].value>0 ? -1 : 0);
			myel = f[this.callback+'_month']; if (myel) myel.selectedIndex = this.displaydate.getMonth() + (myel.options[0].value>0 ? 0 : 1);
			myel = f[this.callback+'_year']; if (myel) {for (var i=0; i<myel.options.length && myel.options[i].value%100<=this.displaydate.getYear()%100; i++); myel.selectedIndex = i - 1;}
			myel = f[this.callback+'_hour']; if (myel) myel.selectedIndex = this.displaydate.getHours() + (myel.options[0].value>0 ? 0 : 1);
			myel = f[this.callback+'_minute']; if (myel) {for (var i=0; i<myel.options.length && myel.options[i].value<=this.displaydate.getMinutes(); i++); myel.selectedIndex = i - 1;}
			if (!myel) this.change (false, ''); //close the calendar if we are only concerned with day/month/year, 16/11/2010
		}
		//The call back is a single form element
		else if (document.getElementById(this.callback) && document.getElementById(this.callback).form) 
			document.getElementById (this.callback).value = showdate;
		else {if (eval ([this.callback] + "(this.name, this.displaydate)")) this.change (false, '');} //call the call back function
	}
	else document.location.href = document.location.pathname + "?calendarname=" + this.name + 
		"&calendartime=" + Math.floor(this.displaydate.getTime()/1000) + //because it's in milliseconds
		"&calendardate=" + this.displaydate.toLocaleString();
}

//Output the table
function Calendar_format() {
	var days = new Array ("Su", "Mo", "Tu", "We", "Th", "Fr", "Sa");
	var firstcol = this.weekends ? 0 : 1; //start from day 0 if we have weekends
	var lastcol = this.weekends ? 6 : 5; //end at day 6 if we have weekends
	//////////////// display the header rows ////////////////
	var r = "<table cellspacing=\"0\">\n"; //start the table
	r += "<tr class=\"calendararrowsandmonth\"><th class=\"calendararrows\">" + this.getLink ("addMonths(-1)", "&lt;") + "</th>\n"; //back a month
	r += "<th class=\"calendarmonth\" colspan=\"" + (this.weekends ? 5 : 3) + "\">"; //colspan of the title
	r += this.displaydate.getMonthName() + " " + this.displaydate.getActualYear(); //the month and year
	r += "</th>\n<th class=\"calendararrows\">" + this.getLink ("addMonths(1)", "&gt;") + "</th>\n</tr>\n"; //add a month
	var cellwidth = Math.floor (100/ (this.weekends ? 7 : 5)); //width of each weekday column
	for (var day=firstcol; day<=lastcol; day++) {r += "<col width=\"" + cellwidth + "%\" />\n";} //width of cells
	r += "<tr class=\"calendardaysofweek\">"; //start the days of the week
	for (var day=firstcol; day<=lastcol; day++) {r += "<th class=\"calendardayofweek\">" + days[day] + "</th>\n";}
	r += "</tr>\n"; //end of days of week
	//////////////// get the start and end day for this month ////////////////
	var td = new Date (this.displaydate.getTime()); //for this we will need to use a temporary date
	var initialnumber = (td.getMonth() == this.initialdate.getMonth()) && (td.getYear() == this.initialdate.getYear()) ?
		this.initialdate.getDate() : 0; //set to the initial date's number or 0
	td.setDate(1); //go to the first of the month
	var startday = td.getDay(); //get the day of the week
	var startdaynumber = td.getTime() / 86400000; //the number of days from the beginning of time (1/1/1970) to the beginning of the month
	td.addMonths(1); //add a month to the temporary date
	var endday = (td.getDay() + 6) % 7; //end on this day of the week (and subtract one)
	var td = new Date(); //reset to now
	var todaynumber = (td.getMonth() == this.displaydate.getMonth()) && (td.getYear() == this.displaydate.getYear()) ?
		td.getDate() : 0; //set to today's date's number or 0
	//the number of days from the start of the month to the days ahead day, added by Paul 17/12/2008 
	var daysaheadnumber = this.daysahead ? Math.floor (td.getTime() / 86400000 + this.daysahead - startdaynumber) : -1000000; 
	//////////////// figuring out starting week and number of weeks ////////////////
	//var numweeks = (startday==0 && endday==6) ? 5 : 6; //the number of weeks to display (4 in February)
	var startweek = 1; //which week to start with
	if (!this.weekends && startday==6) startweek=2; //start on week 2 if the first day is a Sat or Sun
	var numweeks = 6; //maximum number of weeks needed to display this month
	if (endday > startday-2) numweeks = 5; //can be displayed in 5 weeks
	if (startday == 0 && endday == 6) numweeks = 4; //a February starting on a Sunday
	var endweek = numweeks; //the last week do display (so that the last week is shown if the next statement is true)
	if (!this.weekends && (endday%6 < 1)) numweeks--; //end a week early if the last day is a Sat or Sun
	//////////////// display the numbers ////////////////
	for (var week=startweek; week<=numweeks; week++) { //for each week (4 only in February)
		r += "<tr class=\"calendardaysofmonth\">";
		for (var day=firstcol; day<=lastcol; day++) { //for each day of the week
			var daynumber = (week-1)*7 + day - startday + 1; //the day number
			var dayclass="normal"; var daylink=true; //the dayclass and daylink for this day
			if (week==1 && day<startday) {dayclass="blank"; daylink=false;} //before the first day
			else if (week==endweek && day>endday) {dayclass="blank"; daylink=false;} //after the last day
			else if (daynumber==todaynumber) {dayclass="today"; daylink=true;} //todays' date
			else if (daynumber==initialnumber) {dayclass="initial"; daylink=true;} //initial date
			r += '<td id="' + this.fullname + '_' + daynumber + '" class="calendar' + dayclass + '">';
			r += daylink ? (daysaheadnumber < daynumber ? this.getLink ("returnDate(" + daynumber + ")", daynumber) : daynumber) : "&nbsp;";
			r += "</td>\n";
		}
		r += "</tr>\n";
	}
	//////////////// now show the time drop down if we should show minutes ////////////////
	if (this.minutes) {
		r += "<tr class=\"calendartime\"><td class=\"calendartime\" colspan=\"" + (this.weekends ? 7 : 5) + "\">" + this.timetext;
		r += ' ' + this.getTimeDropDown ('hour', 0, 23, 1, this.displaydate.getHours()); //pass in initial hour (display date from 11/2/2010)
		r += ' ' + this.getTimeDropDown ('minute', 0, 59, this.minutes, this.displaydate.getMinutes()); //and in initial minute 
		r += "</td></tr>";
	}
	//////////////// finish the calendar ////////////////
	r += "<tr class=\"calendarclose\"><th class=\"calendarclose\" colspan=\"" + (this.weekends ? 7 : 5) + "\">";
	r += this.getLink ("change('',false)", "close") + "</th></tr>\n"; //end the days of the week
	r += "</table>"; //end the table
	this.change (r, true);
}

//Get the month name from a date object
function Date_getMonthName() {
	var months = new Array ("January", "February", "March", "April", "May", "June",
				"July", "August", "September", "October", "November", "December");
	return months[this.getMonth()];
}
//Get the actual year from a date object
function Date_getActualYear() {var year = this.getYear(); return year < 500 ? year + 1900 : year;}	

//Change the month of a date by adding or subtracting months
function Date_addMonths (howmany) {
	var month = this.getMonth() + howmany;
	this.setYear (this.getActualYear() + Math.floor (month / 12)); //change the year
	this.setMonth ((month+12)%12); //change the month
}

Calendar.prototype.returnDate = Calendar_returnDate;
Calendar.prototype.change = Calendar_change;
Calendar.prototype.getLink = Calendar_getLink;
Calendar.prototype.format = Calendar_format;
Calendar.prototype.addMonths = Calendar_addMonths;
Calendar.prototype.getTimeDropDown = Calendar_getTimeDropDown;
Date.prototype.getMonthName = Date_getMonthName;
Date.prototype.getActualYear = Date_getActualYear;
Date.prototype.addMonths = Date_addMonths;


