/** * @author Gorfdo */// Calendar: a Javascript class for Mootools that adds accessible and unobtrusive date pickers to your form elements <http://electricprism.com/aeron/calendar>// Calendar RC4, Copyright (c) 2007 Aeron Glemann <http://electricprism.com/aeron>, MIT Style License.// Mootools 1.2 compatibility by Davorin Šegovar Calendar = new Class({		Implements: [Options,Events],		options: {		blocked: [], // blocked dates 		classes: [], // ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite']		days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // days of the week starting at sunday		direction: 0, // -1 past, 0 past + future, 1 future		draggable: true,		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],		navigation: 1, // 0 = no nav; 1 = single nav for month; 2 = dual nav for month and year		offset: 0, // first day of the week: 0 = sunday, 1 = monday, etc..		onHideStart: Class.empty,		onHideComplete: Class.empty,		onShowStart: Class.empty,		onShowComplete: Class.empty,		pad: 1, // padding between multiple calendars		tweak: {x: 0, y: 0} // tweak calendar positioning	},	// initialize: calendar constructor	// @param obj (obj) a js object containing the form elements and format strings { id: 'format', id: 'format' etc }	// @param props (obj) optional properties	initialize: function(obj, options) {		// basic error checking		if (!obj) {return false;}		this.setOptions(options);		// create our classes array		var keys = ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite'];		var values = keys.map(function(key, i) {			if (this.options.classes[i]) {				if (this.options.classes[i].length) {key = this.options.classes[i];}			}			return key;		}, this);		this.classes = values.associate(keys);		// create cal element with css styles required for proper cal functioning		this.calendar = new Element('div', { 			'styles': {left: '-1000px', opacity: 0, position: 'absolute', top: '-1000px', zIndex: 1000}		});                this.calendar.addClass(this.classes.calendar);                this.calendar.injectInside(document.body);		// iex 6 needs a transparent iframe underneath the calendar in order to not allow select elements to render through		if (window.ie6) {			this.iframe = new Element('iframe', { 				'styles': {left: '-1000px', position: 'absolute', top: '-1000px', zIndex: 999}			}).injectInside(document.body);			this.iframe.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';		}		// initialize fade method		this.fx = new Fx.Tween(this.calendar, {			onStart: function() { 				if (this.calendar.getStyle('opacity') == 0) { // show					if (window.ie6) {this.iframe.setStyle('display', 'block');}					this.calendar.setStyle('display', 'block');					this.fireEvent('onShowStart', this.element);				}				else { // hide					this.fireEvent('onHideStart', this.element);				}			}.bind(this),			onComplete: function() { 				if (this.calendar.getStyle('opacity') == 0) { // hidden					this.calendar.setStyle('display', 'none');					if (window.ie6) {this.iframe.setStyle('display', 'none');}					this.fireEvent('onHideComplete', this.element);				}				else { // shown					this.fireEvent('onShowComplete', this.element);				}			}.bind(this)		});		// initialize drag method		if (window.Drag && this.options.draggable) {			this.drag = new Drag.Move(this.calendar, { 				onDrag: function() {					if (window.ie6) {this.iframe.setStyles({left: this.calendar.style.left, top: this.calendar.style.top});} 				}.bind(this) 			}); 		}				// create calendars array		this.calendars = [];		var id = 0;		var d = new Date(); // today		d.setDate(d.getDate() + this.options.direction.toInt()); // correct today for directional offset		for (var i in obj) {			var cal = { 				button: new Element('button', {'type': 'button'}),				el: $(i),				els: [],				id: id++,				month: d.getMonth(),				visible: false,				year: d.getFullYear()			};			// fix for bad element (naughty, naughty element!)			if (!this.element(i, obj[i], cal)) {continue;}						cal.el.addClass(this.classes.calendar);			// create cal button			cal.button.addClass(this.classes.calendar).addEvent('click', function(cal) {this.toggle(cal);}.pass(cal, this)).injectAfter(cal.el);			// read in default value			cal.val = this.read(cal);			$extend(cal, this.bounds(cal)); // abs bounds of calendar			$extend(cal, this.values(cal)); // valid days, months, years			this.rebuild(cal);			this.calendars.push(cal); // add to cals array				}		},	// blocked: returns an array of blocked days for the month / year	// @param cal (obj)	// @returns blocked days (array)	blocked: function(cal) {		var blocked = [];		var offset = new Date(cal.year, cal.month, 1).getDay(); // day of the week (offset)		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month				this.options.blocked.each(function(date){			var values = date.split(' ');						// preparation			for (var i = 0; i <= 3; i++){ 				if (!values[i]){values[i] = (i == 3) ? '' : '*';} // make sure blocked date contains values for at least d, m and y				values[i] = values[i].contains(',') ? values[i].split(',') : new Array(values[i]); // split multiple values				var count = values[i].length - 1;				for (var j = count; j >= 0; j--){					if (values[i][j].contains('-')){ // a range						var val = values[i][j].split('-');						for (var k = val[0]; k <= val[1]; k++){							if (!values[i].contains(k)){values[i].push(k + '');}						}						values[i].splice(j, 1);					}				}			}			// execution			if (values[2].contains(cal.year + '') || values[2].contains('*')){				if (values[1].contains(cal.month + 1 + '') || values[1].contains('*')){					values[0].each(function(val){ // if blocked value indicates this month / year						if (val > 0){blocked.push(val.toInt());} // add date to blocked array					});					if (values[3]){ // optional value for day of week						for (var i = 0; i < last; i++){								var day = (i + offset) % 7;									if (values[3].contains(day + '')){ 									blocked.push(i + 1); // add every date that corresponds to the blocked day of the week to the blocked array								}						}					}				}			}		}, this);		return blocked;	},	// bounds: returns the start / end bounds of the calendar	// @param cal (obj)	// @returns obj		bounds: function(cal) {		// 1. first we assume the calendar has no bounds (or a thousand years in either direction)				// by default the calendar will accept a millennium in either direction		var start = new Date(1000, 0, 1); // jan 1, 1000		var end = new Date(2999, 11, 31); // dec 31, 2999		// 2. but if the cal is one directional we adjust accordingly		var date = new Date().getDate() + this.options.direction.toInt();		if (this.options.direction > 0) {			start = new Date();			start.setDate(date + this.options.pad * cal.id);		}				if (this.options.direction < 0) {			end = new Date();			end.setDate(date - this.options.pad * (this.calendars.length - cal.id - 1));		}		// 3. then we can further filter the limits by using the pre-existing values in the selects		cal.els.each(function(el) {				if (el.get('tag') == 'select') {						if (el.format.test('(y|Y)')) { // search for a year select					var years = [];					el.getChildren().each(function(option) { // get options						var values = this.unformat(option.value, el.format);							if (!years.contains(values[0])) {years.push(values[0]);} // add to years array					}, this);						years.sort(this.sort);								if (years[0] > start.getFullYear()) { 						d = new Date(years[0], start.getMonth() + 1, 0); // last day of new month											if (start.getDate() > d.getDate()) {start.setDate(d.getDate());}							start.setYear(years[0]); 					}										if (years.getLast() < end.getFullYear()) { 						d = new Date(years.getLast(), end.getMonth() + 1, 0); // last day of new month											if (end.getDate() > d.getDate()) {end.setDate(d.getDate());}							end.setYear(years.getLast());					}						}					if (el.format.test('(F|m|M|n)')) { // search for a month select					var months_start = [];					var months_end = [];					el.getChildren().each(function(option) { // get options						var values = this.unformat(option.value, el.format);							if ($type(values[0]) != 'number' || values[0] == years[0]) { // if it's a year / month combo for curr year, or simply a month select							if (!months_start.contains(values[1])) {months_start.push(values[1]);} // add to months array						}							if ($type(values[0]) != 'number' || values[0] == years.getLast()) { // if it's a year / month combo for curr year, or simply a month select							if (!months_end.contains(values[1])) {months_end.push(values[1]);} // add to months array						}					}, this);						months_start.sort(this.sort);					months_end.sort(this.sort);										if (months_start[0] > start.getMonth()) { 						d = new Date(start.getFullYear(), months_start[0] + 1, 0); // last day of new month											if (start.getDate() > d.getDate()) {start.setDate(d.getDate());}							start.setMonth(months_start[0]); 					}										if (months_end.getLast() < end.getMonth()) { 						d = new Date(start.getFullYear(), months_end.getLast() + 1, 0); // last day of new month											if (end.getDate() > d.getDate()) {end.setDate(d.getDate());}							end.setMonth(months_end.getLast());					}						}			}		}, this);				return {'start': start, 'end': end};	},	// caption: returns the caption element with header and navigation	// @param cal (obj)	// @returns caption (element)	caption: function(cal) {		// start by assuming navigation is allowed		var navigation = {			prev: {'month': true, 'year': true},			next: {'month': true, 'year': true}		};				// if we're in an out of bounds year		if (cal.year == cal.start.getFullYear()) { 			navigation.prev.year = false; 			if (cal.month == cal.start.getMonth() && this.options.navigation == 1) { 				navigation.prev.month = false;			}				}				if (cal.year == cal.end.getFullYear()) { 			navigation.next.year = false; 			if (cal.month == cal.end.getMonth() && this.options.navigation == 1) { 				navigation.next.month = false;			}		}		// special case of improved navigation but months array with only 1 month we can disable all month navigation		if ($type(cal.months) == 'array') {			if (cal.months.length == 1 && this.options.navigation == 2) {				navigation.prev.month = navigation.next.month = false;			}		}		var caption = new Element('caption');		var prev = new Element('a').addClass(this.classes.prev).appendText('\x3c'); // <				var next = new Element('a').addClass(this.classes.next).appendText('\x3e'); // >		if (this.options.navigation == 2) {			var month = new Element('span').addClass(this.classes.month).injectInside(caption);						if (navigation.prev.month) {prev.clone().addEvent('click', function(cal) {this.navigate(cal, 'm', -1);}.pass(cal, this)).injectInside(month);}						month.adopt(new Element('span').appendText(this.options.months[cal.month]));			if (navigation.next.month) {next.clone().addEvent('click', function(cal) {this.navigate(cal, 'm', 1);}.pass(cal, this)).injectInside(month);}			var year = new Element('span').addClass(this.classes.year).injectInside(caption);			if (navigation.prev.year) {prev.clone().addEvent('click', function(cal) {this.navigate(cal, 'y', -1);}.pass(cal, this)).injectInside(year);}						year.adopt(new Element('span').appendText(cal.year));			if (navigation.next.year) {next.clone().addEvent('click', function(cal) {this.navigate(cal, 'y', 1);}.pass(cal, this)).injectInside(year);}		}		else { // 1 or 0			if (navigation.prev.month && this.options.navigation) {prev.clone().addEvent('click', function(cal) {this.navigate(cal, 'm', -1);}.pass(cal, this)).injectInside(caption);}			caption.adopt(new Element('span').addClass(this.classes.month).appendText(this.options.months[cal.month]));						caption.adopt(new Element('span').addClass(this.classes.year).appendText(cal.year));						if (navigation.next.month && this.options.navigation) {next.clone().addEvent('click', function(cal) {this.navigate(cal, 'm', 1);}.pass(cal, this)).injectInside(caption);}		}		return caption;	},	// changed: run when a select value is changed	// @param cal (obj)	changed: function(cal) {		cal.val = this.read(cal); // update calendar val from inputs			$extend(cal, this.values(cal)); // update bounds - based on curr month		this.rebuild(cal); // rebuild days select		if (!cal.val) {return;} // in case the same date was clicked the cal has no set date we should exit				if (cal.val.getDate() < cal.days[0]) {cal.val.setDate(cal.days[0]);}		if (cal.val.getDate() > cal.days.getLast()) {cal.val.setDate(cal.days.getLast());}				cal.els.each(function(el) {	// then we can set the value to the field			el.value = this.format(cal.val, el.format); 				}, this);				this.check(cal); // checks other cals		this.calendars.each(function(kal) { // update cal graphic if visible			if (kal.visible) {this.display(kal);}		}, this);	},	// check: checks other calendars to make sure no overlapping values	// @param cal (obj)	check: function(cal) {		this.calendars.each(function(kal, i) {			if (kal.val) { // if calendar has value set				var change = false;							if (i < cal.id) { // preceding calendar					var bound = new Date(Date.parse(cal.val));										bound.setDate(bound.getDate() - (this.options.pad * (cal.id - i)));					if (bound < kal.val) {change = true;}				}				if (i > cal.id) { // following calendar					var bound = new Date(Date.parse(cal.val));										bound.setDate(bound.getDate() + (this.options.pad * (i - cal.id)));										if (bound > kal.val) {change = true;}				}				if (change) {					if (kal.start > bound) {bound = kal.start;}					if (kal.end < bound) {bound = kal.end;}					kal.month = bound.getMonth();					kal.year = bound.getFullYear();							$extend(kal, this.values(kal));								// TODO - IN THE CASE OF SELECT MOVE TO NEAREST VALID VALUE					// IN THE CASE OF INPUT DISABLE					// if new date is not valid better unset cal value					// otherwise it would mean incrementally checking to find the nearest valid date which could be months / years away					kal.val = kal.days.contains(bound.getDate()) ? bound : null;					this.write(kal);					if (kal.visible) {this.display(kal);} // update cal graphic if visible				}			}			else {				kal.month = cal.month;				kal.year = cal.year;			}		}, this);	},		// clicked: run when a valid day is clicked in the calendar	// @param cal (obj)	clicked: function(td, day, cal) {		cal.val = (this.value(cal) == day) ? null : new Date(cal.year, cal.month, day); // set new value - if same then disable		this.write(cal); 		// ok - in the special case that it's all selects and there's always a date no matter what (at least as far as the form is concerned)		// we can't let the calendar undo a date selection - it's just not possible!!		if (!cal.val) {cal.val = this.read(cal);}		if (cal.val) {			this.check(cal); // checks other cals									this.toggle(cal); // hide cal		} 		else { // remove active class and replace with valid			td.addClass(this.classes.valid);			td.removeClass(this.classes.active);		}	},		// display: create calendar element	// @param cal (obj)	display: function(cal) {		// 1. header and navigation		this.calendar.empty(); // init div		this.calendar.className = this.classes.calendar + ' ' + this.options.months[cal.month].toLowerCase();		var div = new Element('div').injectInside(this.calendar); // a wrapper div to help correct browser css problems with the caption element		var table = new Element('table').injectInside(div).adopt(this.caption(cal));						// 2. day names				var thead = new Element('thead').injectInside(table);		var tr = new Element('tr').injectInside(thead);				for (var i = 0; i <= 6; i++) {			var th = this.options.days[(i + this.options.offset) % 7];						tr.adopt(new Element('th', {'title': th}).appendText(th.substr(0, 1)));		}		// 3. day numbers		var tbody = new Element('tbody').injectInside(table);		var tr = new Element('tr').injectInside(tbody);		var d = new Date(cal.year, cal.month, 1);		var offset = ((d.getDay() - this.options.offset) + 7) % 7; // day of the week (offset)		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month		var prev = new Date(cal.year, cal.month, 0).getDate(); // last day of previous month		var active = this.value(cal); // active date (if set and within curr month)		var valid = cal.days; // valid days for curr month		var inactive = []; // active dates set by other calendars		var hilited = [];		this.calendars.each(function(kal, i) {			if (kal != cal && kal.val) {				if (cal.year == kal.val.getFullYear() && cal.month == kal.val.getMonth()) {inactive.push(kal.val.getDate());}				if (cal.val) {					for (var day = 1; day <= last; day++) {						d.setDate(day);												if ((i < cal.id && d > kal.val && d < cal.val) || (i > cal.id && d > cal.val && d < kal.val)) { 							if (!hilited.contains(day)) {hilited.push(day);}						}					}				}			}		}, this);		var d = new Date();		var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); // today obv 				for (var i = 1; i < 43; i++) { // 1 to 42 (6 x 7 or 6 weeks)			if ((i - 1) % 7 == 0) {tr = new Element('tr').injectInside(tbody);} // each week is it's own table row			var td = new Element('td').injectInside(tr);									var day = i - offset;			var date = new Date(cal.year, cal.month, day);						var cls = '';						if (day === active) {cls = this.classes.active;} // active			else if (inactive.contains(day)) {cls = this.classes.inactive;} // inactive			else if (valid.contains(day)) {cls = this.classes.valid;} // valid			else if (day >= 1 && day <= last) {cls = this.classes.invalid;} // invalid			if (date.getTime() == today) {cls = cls + ' ' + this.classes.today;} // adds class for today			if (hilited.contains(day)) {cls = cls + ' ' + this.classes.hilite;} // adds class if hilited			td.addClass(cls);			if (valid.contains(day)) { // if it's a valid - clickable - day we add interaction				td.setProperty('title', this.format(date, 'D M jS Y'));								td.addEvents({					'click': function(td, day, cal) { 						this.clicked(td, day, cal); 					}.pass([td, day, cal], this),					'mouseover': function(td, cls) { 						td.addClass(cls); 					}.pass([td, this.classes.hover]),					'mouseout': function(td, cls) { 						td.removeClass(cls); 					}.pass([td, this.classes.hover])				});			}			// pad calendar with last days of prev month and first days of next month			if (day < 1) {day = prev + day;}			else if (day > last) {day = day - last;}			td.appendText(day);		}	},	// element: helper function	// @param el (string) element id	// @param f (string) format string	// @param cal (obj)	element: function(el, f, cal) {		if ($type(f) == 'object') { // in the case of multiple inputs per calendar			for (var i in f) { 				if (!this.element(i, f[i], cal)) {return false;}					}						return true;		}		el = $(el);		if (!el) {return false;}				el.format = f;				if (el.get('tag') == 'select') { // select elements allow the user to manually set the date via select option			el.addEvent('change', function(cal) {this.changed(cal);}.pass(cal, this));		}		else { // input (type text) elements restrict the user to only setting the date via the calendar			el.readOnly = true;			el.addEvent('focus', function(cal) {this.toggle(cal);}.pass(cal, this));		}		cal.els.push(el);		return true;	},	// format: formats a date object according to passed in instructions	// @param date (obj)	// @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y	// @returns string	format: function(date, format) {		var str = '';				if (date) {			var j = date.getDate(); // 1 - 31      var w = date.getDay(); // 0 - 6			var l = this.options.days[w]; // Sunday - Saturday			var n = date.getMonth() + 1; // 1 - 12			var f = this.options.months[n - 1]; // January - December			var y = date.getFullYear() + ''; // 19xx - 20xx						for (var i = 0, len = format.length; i < len; i++) {				var cha = format.charAt(i); // format char								switch(cha) {					// year cases					case 'y': // xx - xx						y = y.substr(2);					case 'Y': // 19xx - 20xx						str += y;						break;						// month cases					case 'm': // 01 - 12						if (n < 10) {n = '0' + n;}					case 'n': // 1 - 12						str += n;						break;						case 'M': // Jan - Dec						f = f.substr(0, 3);					case 'F': // January - December						str += f;						break;						// day cases					case 'd': // 01 - 31						if (j < 10) {j = '0' + j;}					case 'j': // 1 - 31						str += j;						break;						case 'D': // Sun - Sat						l = l.substr(0, 3);					case 'l': // Sunday - Saturday						str += l;						break;						case 'N': // 1 - 7						w += 1;					case 'w': // 0 - 6						str += w;						break;					case 'S': // st, nd, rd or th (works well with j)						if (j % 10 == 1 && j != '11') {str += 'st';}						else if (j % 10 == 2 && j != '12') {str += 'nd';}						else if (j % 10 == 3 && j != '13') {str += 'rd';}						else {str += 'th';}						break;						default:						str += cha;				}			}		}	  return str; //  return format with values replaced	},	// navigate: calendar navigation	// @param cal (obj)	// @param type (str) m or y for month or year	// @param n (int) + or - for next or prev	navigate: function(cal, type, n) {		switch (type) {			case 'm': // month					if ($type(cal.months) == 'array') {						var i = cal.months.indexOf(cal.month) + n; // index of current month												if (i < 0 || i == cal.months.length) { // out of range							if (this.options.navigation == 1) { // if type 1 nav we'll need to increment the year								this.navigate(cal, 'y', n);									}									i = (i < 0) ? cal.months.length - 1 : 0;						}						cal.month = cal.months[i];					}					else { 						var i = cal.month + n;								if (i < 0 || i == 12) {							if (this.options.navigation == 1) {								this.navigate(cal, 'y', n);								}									i = (i < 0) ? 11 : 0;						}												cal.month = i;					}							break;				case 'y': // year					if ($type(cal.years) == 'array') {						var i = cal.years.indexOf(cal.year) + n;						cal.year = cal.years[i]; 					}					else { 						cal.year += n;					}											break;				}		$extend(cal, this.values(cal));		if ($type(cal.months) == 'array') { // if the calendar has a months select			var i = cal.months.indexOf(cal.month); // and make sure the curr months exists for the new year			if (i < 0) {cal.month = cal.months[0];} // otherwise we'll reset the month		}		this.display(cal);	},	// read: compiles cal value based on array of inputs passed in	// @param cal (obj)	// @returns date (obj) or (null)	read: function(cal) {		var arr = [null, null, null];		cal.els.each(function(el) {			// returns an array which may contain empty values			var values = this.unformat(el.value, el.format);						values.each(function(val, i) { 				if ($type(val) == 'number') {arr[i] = val;}			}); 		}, this);		// we can update the cals month and year values		if ($type(arr[0]) == 'number') {cal.year = arr[0];}		if ($type(arr[1]) == 'number') {cal.month = arr[1];}		var val = null;		if (arr.every(function(i) {return $type(i) == 'number';})) { // if valid date			var last = new Date(arr[0], arr[1] + 1, 0).getDate(); // last day of month			if (arr[2] > last) {arr[2] = last;} // make sure we stay within the month (ex in case default day of select is 31 and month is feb)						val = new Date(arr[0], arr[1], arr[2]);		}		return (cal.val == val) ? null : val; // if new date matches old return null (same date clicked twice = disable)	},		// rebuild: rebuilds days + months selects	// @param cal (obj)	rebuild: function(cal) {		cal.els.each(function(el) {						/*			if (el.get('tag') == 'select' && el.format.test('^(F|m|M|n)$')) { // special case for months-only select				if (!cal.options) { cal.options = el.clone(); } // clone a copy of months select							var val = (cal.val) ? cal.val.getMonth() : el.value.toInt();				el.empty(); // initialize select				cal.months.each(function(month) {					// create an option element					var option = new Element('option', {						'selected': (val == month),						'value': this.format(new Date(1, month, 1), el.format);					}).appendText(day).injectInside(el);				}, this);			}			*/			if (el.get('tag') == 'select' && el.format.test('^(d|j)$')) { // special case for days-only select				var d = this.value(cal);				if (!d) {d = el.value.toInt();} // if the calendar doesn't have a set value, try to use value from select				el.empty(); // initialize select				cal.days.each(function(day) {					// create an option element					var option = new Element('option', {						'selected': (d == day),						'value': ((el.format == 'd' && day < 10) ? '0' + day : day)					}).appendText(day).injectInside(el);				}, this);			}		}, this); 	},	// sort: helper function for numerical sorting	sort: function(a, b) {		return a - b;	},	// toggle: show / hide calendar 	// @param cal (obj)	toggle: function(cal) {		document.removeEvent('mousedown', this.fn); // always remove the current mousedown script first					if (cal.visible) { // simply hide curr cal									cal.visible = false;			cal.button.removeClass(this.classes.active); // active						this.fx.start('opacity', 1, 0);		}		else { // otherwise show (may have to hide others)			// hide cal on out-of-bounds click			this.fn = function(e, cal) { 				var e = new Event(e);							var el = e.target;				var stop = false;								while (el != document.body && el.nodeType == 1) {					if (el == this.calendar) {stop = true;}					this.calendars.each(function(kal) {						if (kal.button == el || kal.els.contains(el)) {stop = true;}					});					if (stop) { 						e.stop();						return false;					}					else {el = el.parentNode;}				}								this.toggle(cal);			}.create({'arguments': cal, 'bind': this, 'event': true});							document.addEvent('mousedown', this.fn);			this.calendars.each(function(kal) {				if (kal == cal) {					kal.visible = true;					kal.button.addClass(this.classes.active); // css c-icon-active				}				else {					kal.visible = false;					kal.button.removeClass(this.classes.active); // css c-icon-active				}			}, this);						var size = window.getScrollSize();						var coord = cal.button.getCoordinates();			var x = coord.right + this.options.tweak.x;			var y = coord.top + this.options.tweak.y;			// make sure the calendar doesn't open off screen			if (!this.calendar.coord) {this.calendar.coord = this.calendar.getCoordinates();}			if (x + this.calendar.coord.width > size.x) {x -= (x + this.calendar.coord.width - size.x);}			if (y + this.calendar.coord.height > size.y) {y -= (y + this.calendar.coord.height - size.y);}						this.calendar.setStyles({left: x + 'px', top: y + 'px'});			if (window.ie6) { 				this.iframe.setStyles({height: this.calendar.coord.height + 'px', left: x + 'px', top: y + 'px', width: this.calendar.coord.width + 'px'}); 			}			this.display(cal);						this.fx.start('opacity', 0, 1);		}	},	// unformat: takes a value from an input and parses the d, m and y elements	// @param val (string)	// @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y	// @returns array		unformat: function(val, f) {		f = f.escapeRegExp();				var re = {			d: '([0-9]{2})',			j: '([0-9]{1,2})',			D: '(' + this.options.days.map(function(day) {return day.substr(0, 3);}).join('|') + ')',								l: '(' + this.options.days.join('|') + ')',			S: '(st|nd|rd|th)',			F: '(' + this.options.months.join('|') + ')',			m: '([0-9]{2})',			M: '(' + this.options.months.map(function(month) {return month.substr(0, 3);}).join('|') + ')',								n: '([0-9]{1,2})',			Y: '([0-9]{4})',			y: '([0-9]{2})'		}		var arr = []; // array of indexes		var g = '';		// convert our format string to regexp		for (var i = 0; i < f.length; i++) {			var c = f.charAt(i);						if (re[c]) {				arr.push(c);				g += re[c];			}			else {				g += c;			}		}		// match against date		var matches = val.match('^' + g + '$');				var dates = new Array(3);		if (matches) {			matches = matches.slice(1); // remove first match which is the date			arr.each(function(c, i) {				i = matches[i];								switch(c) {					// year cases					case 'y':						i = '19' + i; // 2 digit year assumes 19th century (same as JS)					case 'Y':						dates[0] = i.toInt();						break;					// month cases					case 'F':						i = i.substr(0, 3);					case 'M':						i = this.options.months.map(function(month) {return month.substr(0, 3);}).indexOf(i) + 1;					case 'm':					case 'n':						dates[1] = i.toInt() - 1;						break;					// day cases					case 'd':					case 'j':						dates[2] = i.toInt();						break;				}			}, this);		}		return dates;	},	// value: returns day value of calendar if set	// @param cal (obj)	// @returns day (int) or null	value: function(cal) {		var day = null;		if (cal.val) {			if (cal.year == cal.val.getFullYear() && cal.month == cal.val.getMonth()) {day = cal.val.getDate();}		}		return day;	},		// values: returns the years, months (for curr year) and days (for curr month and year) for the calendar	// @param cal (obj)	// @returns obj		values: function(cal) {		var years, months, days;		cal.els.each(function(el) {				if (el.get('tag') == 'select') {						if (el.format.test('(y|Y)')) { // search for a year select					years = [];					el.getChildren().each(function(option) { // get options						var values = this.unformat(option.value, el.format);							if (!years.contains(values[0])) {years.push(values[0]);} // add to years array					}, this);						years.sort(this.sort);				}					if (el.format.test('(F|m|M|n)')) { // search for a month select					months = []; // 0 - 11 should be					el.getChildren().each(function(option) { // get options						var values = this.unformat(option.value, el.format);							if ($type(values[0]) != 'number' || values[0] == cal.year) { // if it's a year / month combo for curr year, or simply a month select							if (!months.contains(values[1])) {months.push(values[1]);} // add to months array						}					}, this);						months.sort(this.sort);				}								if (el.format.test('(d|j)') && !el.format.test('^(d|j)$')) { // search for a day select, but NOT a days only select					days = []; // 1 - 31										el.getChildren().each(function(option) { // get options						var values = this.unformat(option.value, el.format);						// in the special case of days we dont want the value if its a days only select						// otherwise that will screw up the options rebuilding						// we will take the values if they are exact dates though						if (values[0] == cal.year && values[1] == cal.month) {							if (!days.contains(values[2])) {days.push(values[2]);} // add to days array						}					}, this);				}			}		}, this);				// we start with what would be the first and last days were there no restrictions		var first = 1;		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of the month				// if we're in an out of bounds year		if (cal.year == cal.start.getFullYear()) {			// in the special case of improved navigation but no months array, we'll need to construct one			if (months == null && this.options.navigation == 2) {				months = [];								for (var i = 0; i < 12; i ++) { 					if (i >= cal.start.getMonth()) {months.push(i);} 				}			}						// if we're in an out of bounds month			if (cal.month == cal.start.getMonth()) { 				first = cal.start.getDate(); // first day equals day of bound			}		}				if (cal.year == cal.end.getFullYear()) {			// in the special case of improved navigation but no months array, we'll need to construct one			if (months == null && this.options.navigation == 2) {				months = [];								for (var i = 0; i < 12; i ++) { 					if (i <= cal.end.getMonth()) {months.push(i);} 				}			}			if (cal.month == cal.end.getMonth()) { 				last = cal.end.getDate(); // last day equals day of bound			}		}		// let's get our invalid days		var blocked = this.blocked(cal);		// finally we can prepare all the valid days in a neat little array		if ($type(days) == 'array') { // somewhere there was a days select			days = days.filter(function(day) {				if (day >= first && day <= last && !blocked.contains(day)) {return day;}			});		}		else { // no days select we'll need to construct a valid days array			days = [];						for (var i = first; i <= last; i++) { 				if (!blocked.contains(i)) {days.push(i);}			}		}				days.sort(this.sort); // sorting our days will give us first and last of month		return {'days': days, 'months': months, 'years': years};	},	// write: sets calendars value to form elements	// @param cal (obj)	write: function(cal) {		this.rebuild(cal);	 // in the case of options, we'll need to make sure we have the correct number of days available				cal.els.each(function(el) {	// then we can set the value to the field			el.value = this.format(cal.val, el.format); 				}, this);	}});Calendar.implement(new Events, new Options);
