function DateSelector(divObj){
	if(divObj == undefined) return;
	EventDispatcher.initialize(this);
	this.GarbageCollect = [];
	this.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
	this.months_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	this.weekdays = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
	this._showWeeks = false;
	this.cellNextMonth = document.createElement('td');
	this.cellNextMonth.appendChild(document.createTextNode('\u00a0'));
	this.cellNextMonth.className = 'DS_pageforward';
	this.cellNextMonth.onclick=this.nextMonth.bind(this);
	this.cellPrevMonth = document.createElement('td');
	this.cellPrevMonth.appendChild(document.createTextNode('\u00a0'));
	this.cellPrevMonth.className = 'DS_pagebackward';
	this.cellPrevMonth.onclick=this.prevMonth.bind(this);

	this.rangeStart = null;
	this.rangeEnd = null;
	
	this.selectYear = null;
	this.yearFrom = null;
	this.yearTo = null;
	this.cellNextMonthDisabled = null;
	this.cellPrevMonthDisabled = null;

	this.selectedDate = null;
	this.setCurrentDate(new Date());
	
	this.calendar = document.createElement('div');
	this.calendar.className = 'DS_calendar';
	
	EventHandler.addEventListener(this.calendar,window.ie ? 'selectstart' : 'mousedown', function(e) {window.stopEvent(e)});
	
	this.setRowWeekdays();
	this.invalidate();
	window.addDOMReadyListener(function(){divObj.appendChild(this.calendar)}.bind(this));
	EventHandler.addEventListener(window,'unload', this.emptyGarbageCollect.bind(this));
	
}
DateSelector.prototype = {
	//public:
	DATE_EVENT : 'onSelectDate',
	WEEK_EVENT : 'onSelectWeek',
	MONTH_EVENT : 'onSelectMonth',
	getSelectedDate : function(){
		return this.selectedDate;
	},
	setSelectedDate : function(date){
		date = date instanceof Date ? date : null;
		this.selectedDate = date;
		this.setCurrentDate(date);
		this.invalidate();
	},
	setSelectedMonth : function(year, month){/*month is zero based*/	
		var d = new Date();
		d.setFullYear(year,month,1);
		this.currentYear = d.getFullYear();
		this.currentMonth = d.getMonth();
		this.invalidate();
	},
	showWeeks : function(bln){
		if(this._showWeeks != bln){
			this._showWeeks = bln;
			this.setRowWeekdays();
			this.invalidate();
		}
	},
	setRange : function(s,e) {
		this.rangeStart = s;
		this.rangeEnd = e;
		this.invalidate();
	},
	setYearRange : function(f, t){
		if(typeof f == 'number' && typeof t == 'number' && f < t) {	
			this.yearFrom = f;
			this.yearTo = t;
			this.cellNextMonthDisabled = document.createElement('td');
			this.cellNextMonthDisabled.appendChild(document.createTextNode('\u00a0'));
			this.cellNextMonthDisabled.className = 'DS_pageforward_disabled';
			this.cellPrevMonthDisabled = document.createElement('td');
			this.cellPrevMonthDisabled.appendChild(document.createTextNode('\u00a0'));
			this.cellPrevMonthDisabled.className = 'DS_pagebackward_disabled';
			this.selectYear = document.createElement('select');
			this.selectYear.className = 'DS_year';
			EventHandler.addEventListener(this.selectYear,'change', this.onSelectYear.bind(this, this.selectYear));

			for(var i=t; i>=f; i--){
				var o = document.createElement('option');
				if(this.currentYear == i) o.selected = true;
				o.value = i;
				o.appendChild(document.createTextNode(i.toString())); 
				this.selectYear.appendChild(o);
			}
			this.invalidate();
		}
	},
	dateFormatter : function(d){
		return d.toDateString();//default output
	},
	//private:
	setRowWeekdays : function(){
		this.rowWeekdays = document.createElement('tr');
		var a = this.weekdays.slice();
		if(this._showWeeks)a.unshift('\u00a0');
		var n = a.length;
		var i = -1;
		while(++i<n){
			c = document.createElement('td');
			c.className = 'DS_weekday';
			c.appendChild(document.createTextNode(a[i]));
			this.rowWeekdays.appendChild(c);
		}
	},
	getWeek : function(date) {
		return parseInt((date.getTime() - new Date(date.getFullYear(),0,1).getTime())/604800000 + 1);
	},
	isSelectedDate : function(date){
		if(this.selectedDate)return this.compareDates(this.selectedDate, date) == 0;
		return false;
	},
	compareDates : function(date1, date2){
		//Returns -1 if d1 is greater than d2. Returns 1 if d2 is greater than d1. Returns 0 if both dates are equal.
		var y1 = date1.getFullYear(), y2 = date2.getFullYear(), m1 = date1.getMonth(), m2 = date2.getMonth(), d1 = date1.getDate(), d2 = date2.getDate();
		date1 = new Date(y1, m1, d1);//skip time
		date2 = new Date(y2, m2, d2);
		if(date1 > date2) return -1;
		else if(date1 < date2) return 1;
		else if(y1 == y2 && m1 == m2 && d1 == d2) return 0;
		else return;
	},
	setCurrentDate : function(date){
		date = date instanceof Date ? date : new Date();
		this.currentDate = date;
		this.currentMonth = date.getMonth();
		this.currentYear = date.getFullYear();
	},
	prevMonth : function(){
		this.setCurrentDate(this.currentMonth > 0 ? new Date(this.currentYear, this.currentMonth - 1, 1) : new Date(this.currentYear - 1, 11, 1));//detect correct month/year for KHTML
		this.invalidate();
	},
	nextMonth : function(){
		this.setCurrentDate(new Date(this.currentYear, this.currentMonth + 1, 1));//works in all browsers
		this.invalidate();
	},
	onSelectYear : function(e){
		this.setCurrentDate(new Date(e.options[e.selectedIndex].value, this.currentMonth));	
		this.invalidate();
	},
	setClassName : function (cell, date){
		cell.className = this.isSelectedDate(date) ? 'DS_date_selected' : this.compareDates(new Date(), date) == 0 ? 'DS_today' : 'DS_date';
	},
	setSelected : function (cell, date) {
		if(this.selectedCell) var c = this.selectedCell, d = this.selectedDate;
		if(this.selectedCell === cell){
			this.selectedCell = null;
			this.selectedDate = null;
		}else{
			this.selectedDate = date;
			this.selectedCell = cell;
			this.setClassName(cell, date);
		}
		if(c) this.setClassName(c, d);
		this.dispatchEvent(this.DATE_EVENT, this.selectedDate?this.dateFormatter(this.selectedDate):null);
	},
	invalidate : function(){
		clearTimeout(this.timer);
		this.timer = this.onInvalidate.delay(0, this);
	},
	onInvalidate : function(){
		this.emptyGarbageCollect();
		this.selectedCell = null;
		var n = this.weekdays.length;
		var tbl = document.createElement('table');
		var thd = document.createElement('thead');
		var tbd = document.createElement('tbody');
		tbl.appendChild(thd);
		tbl.appendChild(tbd);

		//header
		var r = document.createElement('tr');
		var c = document.createElement('td');
		c.setAttribute('colSpan', String(n-(this._showWeeks?1:2)));//IE:colSpan
		c.className = 'DS_month';
		
		if(this.selectYear){
			r.appendChild((this.currentYear == this.yearFrom && this.currentMonth == 0) ? this.cellPrevMonthDisabled : this.cellPrevMonth);
			c.appendChild(document.createTextNode(this.months_short[this.currentMonth] + '\u00a0'));
			if(this.selectYear.options[this.selectYear.selectedIndex].value != this.currentYear){
				var i = -1;
				var l = this.selectYear.options.length;
				while(++i<l){
					if(this.currentYear == this.selectYear.options[i].value){
						this.selectYear.selectedIndex = i;
						break;
					}
				}
			}
			c.appendChild(this.selectYear);
			r.appendChild(c);
			r.appendChild((this.currentYear == this.yearTo && this.currentMonth == 11) ? this.cellNextMonthDisabled : this.cellNextMonth);
		}else{
			r.appendChild(this.cellPrevMonth);
			c.appendChild(document.createTextNode(this.months[this.currentMonth]+ '\u00a0' + this.currentYear));
			c.onclick = this.dispatchEvent.bind(this, this.MONTH_EVENT, this.currentYear + '-' + (this.currentMonth+1));
			this.GarbageCollect.push(c);
			r.appendChild(c);
			r.appendChild(this.cellNextMonth);
		}
		
		thd.appendChild(r);
		
		//weekdays
		tbd.appendChild(this.rowWeekdays);

		//dates:
		var d = new Date(this.currentYear, this.currentMonth, 1);
		d.setDate(d.getDate() - d.getDay());
		var i = -1, m;
		while (++i < 6) {
			r = document.createElement('tr');
			var j = -1;
			while (++j < n) {
				m = d.getMonth() == this.currentMonth;
				if(this._showWeeks && j==0){
					c = document.createElement('td');
					if(i==0 || m){
						var week = this.getWeek(d);
						c.className = 'DS_week';
						c.appendChild(document.createTextNode(week.toString()));
						c.onclick = this.dispatchEvent.bind(this, this.WEEK_EVENT, this.currentYear+'-'+week);
						this.GarbageCollect.push(c);
					}else c.appendChild(document.createTextNode('\u00a0'));
					r.appendChild(c);
				}
				c = document.createElement('td');
				if(m) {
					if((this.rangeStart && this.compareDates(d,this.rangeStart)==1) || (this.rangeEnd && this.compareDates(d,this.rangeEnd)==-1)) c.className = 'DS_date_disabled';
					else{
						if(this.isSelectedDate(d)) this.selectedCell = c;
						this.setClassName(c, d);
						c.onclick = this.setSelected.bind(this, c, new Date(d));
					}	
					c.appendChild(document.createTextNode(d.getDate()));
				}else c.appendChild(document.createTextNode('\u00a0'));//&nbsp;

				d.setDate(d.getDate() + 1);
				r.appendChild(c);
				this.GarbageCollect.push(c);
			}
			tbd.appendChild(r);
		}
		while(this.calendar.hasChildNodes()) this.calendar.removeChild(this.calendar.firstChild);
		this.calendar.appendChild(tbl);
	},
	emptyGarbageCollect : function(){
		var l = this.GarbageCollect.length,e;
		while(l--){
			var e=this.GarbageCollect[l];
			e.onclick = null;
		}
		this.GarbageCollect.length = 0;
	}
}
function DateField(inputObj){
	if(inputObj == undefined) return;
	DateSelector.call(this, document.body);
	this.inputObj = inputObj;
	this.calendar.style.display = 'none';
	this.addEventListener(this.DATE_EVENT, this[this.DATE_EVENT].bind(this));
	EventHandler.addEventListener(document,'mouseup', this.onMouseUp.bindAsEventListener(this));
}
DateField.prototype = new DateSelector();
$extend(DateField.prototype,{
	toggle : function(){
		if(this.calendar.style.display == 'block') this.hideCalendar();
		else this.showCalendar();
	},
	positionCalendar : function(){
		var pos = document.cumulativeOffset(this.inputObj);
		this.calendar.style.top = (pos.top + this.inputObj.offsetHeight - 1) + 'px';
		this.calendar.style.left = (pos.left + (this.inputObj.offsetWidth - this.calendar.offsetWidth)) + 'px';
	},
	showCalendar : function(){
		this.calendar.style.display = 'block';
		this.positionCalendar();//in case window is resized
	},
	hideCalendar : function(){
		this.calendar.style.display = 'none';
	},	
	onMouseUp : function(event){
		if(event){
			var t = event.target || event.srcElement;
			while(t){
				//try{
					if(t === this.calendar) return;
					if(t === this.inputObj){
						this.toggle();
						return;
					}
					t = t.parentNode;
				//}catch(err){
				//	return;
				//}
			}
			this.hideCalendar();
		}
	},
	onSelectDate : function(date){
		this.inputObj.value = date?date:'';
		this.hideCalendar();
	}
});

