// By Matt Brown // June-October 2006 // email: dowdybrown@yahoo.com // Inspired by code originally written by Tan Ling Wee on 2 Dec 2001 // Requires prototype.js and ricoCommon.js Rico.CalendarControl = Class.create( /** @lends Rico.CalendarControl# */ { /** * @class Implements a pop-up Gregorian calendar. * Dates of adoption of the Gregorian calendar vary by country - accurate as a US & British calendar from 14 Sept 1752 to present. * Mark special dates with calls to addHoliday() * @extends Rico.Popup * @constructs * @param id unique identifier * @param options object may contain any of the following:
*
startAt
week starts with 0=sunday, 1=monday? default=0
*
showWeekNumber
show week number in first column? default=0
*
showToday
show "Today is..." in footer? default=1
*
cursorColor
color used to highlight dates as the user moves their mouse, default=#FDD
*
repeatInterval
when left/right arrow is pressed, repeat action every x milliseconds, default=100
*
dateFmt
date format for return value (one of values accepted by {@link Date#formatDate}), default=ISO8601
*
selectedDateBorder
border to indicate currently selected date? default=#666666
*
minDate
earliest selectable date? default=today-50 years
*
maxDate
last selectable date? default=today+50 years
*
*/ initialize: function(id,options) { this.id=id; var today=new Date(); Object.extend(this, new Rico.Popup({ignoreClicks:true})); Object.extend(this.options, { startAt : 0, showWeekNumber : 0, showToday : 1, cursorColor: '#FDD', repeatInterval : 100, dateFmt : 'ISO8601', selectedDateBorder : "#666666", minDate : new Date(today.getFullYear()-50,0,1), maxDate : new Date(today.getFullYear()+50,11,31) }); Object.extend(this.options, options || {}); /** * alias for closePopup * @function */ this.close=this.closePopup; this.bPageLoaded=false; this.img=[]; this.Holidays={}; this.weekString=RicoTranslate.getPhraseById("calWeekHdg"); this.re=/^\s*(\w+)(\W)(\w+)(\W)(\w+)/i; this.setDateFmt(this.options.dateFmt); }, setDateFmt: function(fmt) { this.dateFmt=(fmt=='rico') ? RicoTranslate.dateFmt : fmt; this.dateParts=[]; if (this.re.exec(this.dateFmt)) { this.dateParts[RegExp.$1]=0; this.dateParts[RegExp.$3]=1; this.dateParts[RegExp.$5]=2; } }, /** * Call before displaying calendar to highlight special days * @param d day (1-31) * @param m month (1-12) * @param y year (0 implies a repeating holiday) * @param desc description * @param bgColor background color for cell displaying this day (CSS value, defaults to '#DDF') * @param txtColor text color for cell displaying this day (CSS value), if not specified it is displayed with the same color as other days */ addHoliday : function(d, m, y, desc, bgColor, txtColor) { this.Holidays[this.holidayKey(y,m-1,d)]={desc:desc, txtColor:txtColor, bgColor:bgColor || '#DDF'}; }, /** @private */ holidayKey : function(y,m,d) { return 'h'+y.toPaddedString(4)+m.toPaddedString(2)+d.toPaddedString(2); }, atLoad : function() { this.container=document.createElement("div"); this.container.style.display="none"; this.container.id=this.id; this.container.className='ricoCalContainer'; this.maintab=document.createElement("table"); this.maintab.cellSpacing=0; this.maintab.cellPadding=0; this.maintab.border=0; this.maintab.className='ricoCalTab'; var r,c,i,j,img,dow,a,s; for (i=0; i<7; i++) { r=this.maintab.insertRow(-1); r.className='row'+i; for (c=0; c<8; c++) { r.insertCell(-1); } } this.tbody=this.maintab.tBodies[0]; r=this.tbody.rows[0]; r.className='ricoCalDayNames'; if (this.options.showWeekNumber) { r.cells[0].innerHTML=this.weekString; for (i=0; i<7; i++) { this.tbody.rows[i].cells[0].className='ricoCalWeekNum'; } } this.styles=[]; for (i=0; i<7; i++) { dow=(i+this.options.startAt) % 7; r.cells[i+1].innerHTML=RicoTranslate.dayAbbr(dow); this.styles[i+1]='ricoCal'+dow; } // table header (navigation controls) this.thead=this.maintab.createTHead(); r=this.thead.insertRow(-1); c=r.insertCell(-1); c.colSpan=8; img=this.createNavArrow('decMonth','left'); c.appendChild(document.createElement("a")).appendChild(img); this.titleMonth=document.createElement("a"); c.appendChild(this.titleMonth); Event.observe(this.titleMonth,"click", this.popUpMonth.bindAsEventListener(this), false); img=this.createNavArrow('incMonth','right'); c.appendChild(document.createElement("a")).appendChild(img); s=document.createElement("span"); s.innerHTML=' '; s.style.paddingLeft='3em'; c.appendChild(s); img=this.createNavArrow('decYear','left'); c.appendChild(document.createElement("a")).appendChild(img); this.titleYear=document.createElement("a"); Event.observe(this.titleYear,"click", this.popUpYear.bindAsEventListener(this), false); c.appendChild(this.titleYear); img=this.createNavArrow('incYear','right'); c.appendChild(document.createElement("a")).appendChild(img); // table footer (today) if (this.options.showToday) { this.tfoot=this.maintab.createTFoot(); r=this.tfoot.insertRow(-1); this.todayCell=r.insertCell(-1); this.todayCell.colSpan=8; Event.observe(this.todayCell,"click", this.selectNow.bindAsEventListener(this), false); } this.container.appendChild(this.maintab); // close icon (upper right) img=document.createElement("img"); img.src=Rico.imgDir+'close.gif'; img.onclick=this.close.bind(this); img.style.cursor='pointer'; img.style.position='absolute'; img.style.top='1px'; /* assumes a 1px border */ img.style.right='1px'; img.title=RicoTranslate.getPhraseById('close'); this.container.appendChild(img); // month selector this.monthSelect=document.createElement("table"); this.monthSelect.className='ricoCalMenu'; this.monthSelect.cellPadding=2; this.monthSelect.cellSpacing=0; this.monthSelect.border=0; for (i=0; i<4; i++) { r=this.monthSelect.insertRow(-1); for (j=0; j<3; j++) { c=r.insertCell(-1); a=document.createElement("a"); a.innerHTML=RicoTranslate.monthAbbr(i*3+j); a.name=i*3+j; c.appendChild(a); Event.observe(a,"click", this.selectMonth.bindAsEventListener(this), false); } } this.monthSelect.style.display='none'; this.container.appendChild(this.monthSelect); // year selector this.yearPopup=document.createElement("div"); this.yearPopup.style.display="block"; this.yearPopup.className='ricoCalYearPrompt'; this.container.appendChild(this.yearPopup); this.yearPopupSpan=this.yearPopup.appendChild(document.createElement("span")); this.yearPopupYear=this.yearPopup.appendChild(document.createElement("input")); this.yearPopupYear.maxlength=4; this.yearPopupYear.size=4; Event.observe(this.yearPopupYear,"keypress", this.yearKey.bindAsEventListener(this), false); img=document.createElement("img"); img.src=Rico.imgDir+'checkmark.gif'; Event.observe(img,"click", this.processPopUpYear.bindAsEventListener(this), false); this.yearPopup.appendChild(img); img=document.createElement("img"); img.src=Rico.imgDir+'delete.gif'; Event.observe(img,"click", this.popDownYear.bindAsEventListener(this), false); this.yearPopup.appendChild(img); // fix anchors so they work in IE6 a=this.container.getElementsByTagName('a'); for (i=0; i this.options.maxDate.getFullYear()) return false; if (yr == this.options.maxDate.getFullYear() && mo > this.options.maxDate.getMonth()) return false; return true; }, incMonth : function() { var newMonth=this.monthSelected+1; var newYear=this.yearSelected; if (newMonth>11) { newMonth=0; newYear++; } if (!this.isValidMonth(newYear,newMonth)) return; this.monthSelected=newMonth; this.yearSelected=newYear; this.constructCalendar(); }, decMonth : function() { var newMonth=this.monthSelected-1; var newYear=this.yearSelected; if (newMonth<0) { newMonth=11; newYear--; } if (!this.isValidMonth(newYear,newMonth)) return; this.monthSelected=newMonth; this.yearSelected=newYear; this.constructCalendar(); }, /** @private */ selectMonth : function(e) { var el=Event.element(e); this.monthSelected=parseInt(el.name,10); this.constructCalendar(); Event.stop(e); }, popUpMonth : function() { Element.toggle(this.monthSelect); this.monthSelect.style.top=(this.thead.offsetHeight+2)+'px'; this.monthSelect.style.left=this.titleMonth.offsetLeft+'px'; }, popDownMonth : function() { Element.hide(this.monthSelect); }, popDownYear : function() { Element.hide(this.yearPopup); this.yearPopup.disabled=true; // make sure this does not get submitted }, /** * Prompt for year */ popUpYear : function() { Element.toggle(this.yearPopup); if (!Element.visible(this.yearPopup)) return; this.yearPopup.disabled=false; this.yearPopup.style.left='120px'; this.yearPopup.style.top=(this.thead.offsetHeight+2)+'px'; this.yearPopupSpan.innerHTML=' '+RicoTranslate.getPhraseById("calYearRange",this.options.minDate.getFullYear(),this.options.maxDate.getFullYear())+'
'; this.yearPopupYear.value=''; // this.yearSelected this.yearPopupYear.focus(); }, yearKey : function(e) { switch (RicoUtil.eventKey(e)) { case 27: this.popDownYear(); Event.stop(e); return false; case 13: this.processPopUpYear(); Event.stop(e); return false; } return true; }, processPopUpYear : function() { var newYear=this.yearPopupYear.value; newYear=parseInt(newYear,10); if (isNaN(newYear) || newYearthis.options.maxDate.getFullYear()) { alert(RicoTranslate.getPhraseById("calInvalidYear")); } else { this.yearSelected=newYear; this.popDownYear(); this.constructCalendar(); } }, incYear : function() { if (this.yearSelected>=this.options.maxDate.getFullYear()) return; this.yearSelected++; this.constructCalendar(); }, decYear : function() { if (this.yearSelected<=this.options.minDate.getFullYear()) return; this.yearSelected--; this.constructCalendar(); }, // tried a number of different week number functions posted on the net // this is the only one that produced consistent results when comparing week numbers for December and the following January WeekNbr : function(year,month,day) { var when = new Date(year,month,day); var newYear = new Date(year,0,1); var offset = 7 + 1 - newYear.getDay(); if (offset == 8) offset = 1; var daynum = ((Date.UTC(year,when.getMonth(),when.getDate(),0,0,0) - Date.UTC(year,0,1,0,0,0)) /1000/60/60/24) + 1; var weeknum = Math.floor((daynum-offset+7)/7); if (weeknum == 0) { year--; var prevNewYear = new Date(year,0,1); var prevOffset = 7 + 1 - prevNewYear.getDay(); weeknum = (prevOffset == 2 || prevOffset == 8) ? 53 : 52; } return weeknum; }, constructCalendar : function() { var aNumDays = [31,0,31,30,31,30,31,31,30,31,30,31]; var startDate = new Date (this.yearSelected,this.monthSelected,1); var endDate,numDaysInMonth,i,colnum; if (typeof this.monthSelected!='number' || this.monthSelected>=12 || this.monthSelected<0) { alert('ERROR in calendar: monthSelected='+this.monthSelected); return; } if (this.monthSelected==1) { endDate = new Date (this.yearSelected,this.monthSelected+1,1); endDate = new Date (endDate - (24*60*60*1000)); numDaysInMonth = endDate.getDate(); } else { numDaysInMonth = aNumDays[this.monthSelected]; } var dayPointer = startDate.getDay() - this.options.startAt; if (dayPointer<0) dayPointer+=7; this.popDownMonth(); this.popDownYear(); this.bgcolor=Element.getStyle(this.tbody,'background-color'); this.bgcolor=this.bgcolor.replace(/\"/g,''); if (this.options.showWeekNumber) { for (i=1; i<7; i++) { this.tbody.rows[i].cells[0].innerHTML=' '; } } for ( i=1; i<=dayPointer; i++ ) { this.resetCell(this.tbody.rows[1].cells[i]); } for ( var datePointer=1,r=1; datePointer<=numDaysInMonth; datePointer++,dayPointer++ ) { colnum=dayPointer % 7 + 1; if (this.options.showWeekNumber==1 && colnum==1) { this.tbody.rows[r].cells[0].innerHTML=this.WeekNbr(this.yearSelected,this.monthSelected,datePointer); } var dateClass=this.styles[colnum]; if ((datePointer==this.dateNow)&&(this.monthSelected==this.monthNow)&&(this.yearSelected==this.yearNow)) { dateClass='ricoCalToday'; } var c=this.tbody.rows[r].cells[colnum]; c.innerHTML=" " + datePointer + " "; c.className=dateClass; var bordercolor=(datePointer==this.odateSelected) && (this.monthSelected==this.omonthSelected) && (this.yearSelected==this.oyearSelected) ? this.options.selectedDateBorder : this.bgcolor; c.style.border='1px solid '+bordercolor; var h=this.Holidays[this.holidayKey(this.yearSelected,this.monthSelected,datePointer)]; if (!h) { h=this.Holidays[this.holidayKey(0,this.monthSelected,datePointer)]; } c.style.color=h ? h.txtColor : ''; c.style.backgroundColor=h ? h.bgColor : ''; c.title=h ? h.desc : ''; if (colnum==7) r++; } while (dayPointer<42) { colnum=dayPointer % 7 + 1; this.resetCell(this.tbody.rows[r].cells[colnum]); dayPointer++; if (colnum==7) r++; } this.titleMonth.innerHTML = RicoTranslate.monthAbbr(this.monthSelected); this.titleYear.innerHTML = this.yearSelected; if (this.todayCell) { this.todayCell.innerHTML = RicoTranslate.getPhraseById("calToday",this.dateNow,RicoTranslate.monthAbbr(this.monthNow),this.yearNow,this.monthNow+1); } }, /** @private */ resetCell: function(c) { c.innerHTML=" "; c.className='ricoCalEmpty'; c.style.border='1px solid '+this.bgcolor; c.style.color=''; c.style.backgroundColor=''; c.title=''; }, /** @private */ saveAndClose : function(e) { Event.stop(e); var el=Event.element(e); var s=el.innerHTML.replace(/ /g,''); if (s=='' || el.className=='ricoCalWeekNum') return; var day=parseInt(s,10); if (isNaN(day)) return; var d=new Date(this.yearSelected,this.monthSelected,day); var dateStr=d.formatDate(this.dateFmt=='ISO8601' ? 'yyyy-mm-dd' : this.dateFmt); if (this.returnValue) this.returnValue(dateStr); this.close(); }, open : function(curval) { if (!this.bPageLoaded) return; var today = new Date(); this.dateNow = today.getDate(); this.monthNow = today.getMonth(); this.yearNow = today.getFullYear(); if (typeof curval=='object') { this.dateSelected = curval.getDate(); this.monthSelected = curval.getMonth(); this.yearSelected = curval.getFullYear(); } else if (this.dateFmt=='ISO8601') { var d=new Date(); d.setISO8601(curval); this.dateSelected = d.getDate(); this.monthSelected = d.getMonth(); this.yearSelected = d.getFullYear(); } else if (this.re.exec(curval)) { var aDate = [ RegExp.$1, RegExp.$3, RegExp.$5 ]; this.dateSelected = parseInt(aDate[this.dateParts.dd], 10); this.monthSelected = parseInt(aDate[this.dateParts.mm], 10) - 1; this.yearSelected = parseInt(aDate[this.dateParts.yyyy], 10); if (this.yearSelected < 100) { // apply a century to 2-digit years this.yearSelected+=this.yearNow - (this.yearNow % 100); var maxyr=this.options.maxDate.getFullYear(); while (this.yearSelected > maxyr) this.yearSelected-=100; } } else { if (curval) { alert('ERROR: invalid date passed to calendar ('+curval+')'); } this.dateSelected = this.dateNow; this.monthSelected = this.monthNow; this.yearSelected = this.yearNow; } this.odateSelected=this.dateSelected; this.omonthSelected=this.monthSelected; this.oyearSelected=this.yearSelected; this.constructCalendar(); this.openPopup(); } }); Rico.includeLoaded('ricoCalendar.js');