3 // email: dowdybrown@yahoo.com
\r
4 // Implements a pop-up Gregorian calendar.
\r
5 // Dates of adoption of the Gregorian calendar vary by country - accurate as a US & British calendar from 14 Sept 1752 to present.
\r
6 // Mark special dates with calls to addHoliday()
\r
7 // Inspired by code originally written by Tan Ling Wee on 2 Dec 2001
\r
9 // Requires prototype.js and ricoCommon.js
\r
11 Rico.CalendarControl = Class.create();
\r
13 Rico.CalendarControl.prototype = {
\r
15 initialize: function(id,options) {
\r
17 var today=new Date();
\r
18 Object.extend(this, new Rico.Popup({ignoreClicks:true}));
\r
19 Object.extend(this.options, {
\r
20 startAt : 0, // week starts with 0=sunday, 1=monday
\r
21 showWeekNumber : 0, // show week number in first column?
\r
22 showToday : 1, // show "Today is..." in footer?
\r
23 cursorColor: '#FDD', // color used to highlight dates as the user moves their mouse
\r
24 repeatInterval : 100, // when left/right arrow is pressed, repeat action every x milliseconds
\r
25 dateFmt : 'ISO8601', // default is ISO-8601, 'rico'=use format stored in ricoTranslate object
\r
26 selectedDateBorder : "#666666", // border to indicate currently selected date
\r
27 minDate : new Date(today.getFullYear()-50,0,1), // default to +-50 yrs from current date
\r
28 maxDate : new Date(today.getFullYear()+50,11,31)
\r
30 Object.extend(this.options, options || {});
\r
31 this.close=this.closePopup;
\r
32 this.bPageLoaded=false;
\r
33 this.img=new Array();
\r
35 this.todayString=RicoTranslate.getPhrase("Today is ");
\r
36 this.weekString=RicoTranslate.getPhrase("Wk");
\r
37 if (this.options.dateFmt=='rico') this.options.dateFmt=RicoTranslate.dateFmt;
\r
38 this.dateParts=new Array();
\r
39 this.re=/^\s*(\w+)(\W)(\w+)(\W)(\w+)/i;
\r
40 if (this.re.exec(this.options.dateFmt)) {
\r
41 this.dateParts[RegExp.$1]=0;
\r
42 this.dateParts[RegExp.$3]=1;
\r
43 this.dateParts[RegExp.$5]=2;
\r
48 // y=0 implies a repeating holiday
\r
49 addHoliday : function(d, m, y, desc, bgColor, txtColor) {
\r
50 this.Holidays[this.holidayKey(y,m-1,d)]={desc:desc, txtColor:txtColor, bgColor:bgColor || '#DDF'};
\r
53 holidayKey : function(y,m,d) {
\r
54 return 'h'+y.toPaddedString(4)+m.toPaddedString(2)+d.toPaddedString(2);
\r
57 atLoad : function() {
\r
58 this.container=document.createElement("div");
\r
59 this.container.style.display="none"
\r
60 this.container.id=this.id;
\r
61 this.container.className='ricoCalContainer';
\r
63 this.maintab=document.createElement("table");
\r
64 this.maintab.cellSpacing=0;
\r
65 this.maintab.cellPadding=0;
\r
66 this.maintab.border=0;
\r
67 this.maintab.className='ricoCalTab';
\r
69 for (var i=0; i<7; i++) {
\r
70 var r=this.maintab.insertRow(-1);
\r
71 r.className='row'+i;
\r
72 for (var c=0; c<8; c++)
\r
75 this.tbody=this.maintab.tBodies[0];
\r
76 var r=this.tbody.rows[0];
\r
77 r.className='ricoCalDayNames';
\r
78 if (this.options.showWeekNumber) {
\r
79 r.cells[0].innerHTML=this.weekString;
\r
80 for (var i=0; i<7; i++)
\r
81 this.tbody.rows[i].cells[0].className='ricoCalWeekNum';
\r
84 for (var i=0; i<7; i++) {
\r
85 var dow=(i+this.options.startAt) % 7;
\r
86 r.cells[i+1].innerHTML=RicoTranslate.dayNames[dow].substring(0,3);
\r
87 this.styles[i+1]='ricoCal'+dow;
\r
90 // table header (navigation controls)
\r
91 this.thead=this.maintab.createTHead()
\r
92 var r=this.thead.insertRow(-1);
\r
93 var c=r.insertCell(-1);
\r
95 var img=this.createNavArrow('decMonth','left');
\r
96 c.appendChild(document.createElement("a")).appendChild(img);
\r
97 this.titleMonth=document.createElement("a");
\r
98 c.appendChild(this.titleMonth);
\r
99 Event.observe(this.titleMonth,"click", this.popUpMonth.bindAsEventListener(this), false);
\r
100 var img=this.createNavArrow('incMonth','right');
\r
101 c.appendChild(document.createElement("a")).appendChild(img);
\r
102 var s=document.createElement("span");
\r
103 s.innerHTML=' ';
\r
104 s.style.paddingLeft='3em';
\r
107 var img=this.createNavArrow('decYear','left');
\r
108 c.appendChild(document.createElement("a")).appendChild(img);
\r
109 this.titleYear=document.createElement("a");
\r
110 Event.observe(this.titleYear,"click", this.popUpYear.bindAsEventListener(this), false);
\r
111 c.appendChild(this.titleYear);
\r
112 var img=this.createNavArrow('incYear','right');
\r
113 c.appendChild(document.createElement("a")).appendChild(img);
\r
115 // table footer (today)
\r
116 if (this.options.showToday) {
\r
117 this.tfoot=this.maintab.createTFoot()
\r
118 var r=this.tfoot.insertRow(-1);
\r
119 this.todayCell=r.insertCell(-1);
\r
120 this.todayCell.colSpan=8;
\r
121 Event.observe(this.todayCell,"click", this.selectNow.bindAsEventListener(this), false);
\r
125 this.container.appendChild(this.maintab);
\r
127 // close icon (upper right)
\r
128 var img=document.createElement("img");
\r
129 img.src=Rico.imgDir+'close.gif';
\r
130 img.onclick=this.close.bind(this);
\r
131 img.style.cursor='pointer';
\r
132 img.style.position='absolute';
\r
133 img.style.top='1px'; /* assumes a 1px border */
\r
134 img.style.right='1px';
\r
135 this.container.appendChild(img);
\r
138 this.monthSelect=document.createElement("table");
\r
139 this.monthSelect.className='ricoCalMenu';
\r
140 this.monthSelect.cellPadding=2;
\r
141 this.monthSelect.cellSpacing=0;
\r
142 this.monthSelect.border=0;
\r
143 for (var i=0; i<4; i++) {
\r
144 var r=this.monthSelect.insertRow(-1);
\r
145 for (var j=0; j<3; j++) {
\r
146 var c=r.insertCell(-1);
\r
147 var a=document.createElement("a");
\r
148 a.innerHTML=RicoTranslate.monthNames[i*3+j].substring(0,3);
\r
151 Event.observe(a,"click", this.selectMonth.bindAsEventListener(this), false);
\r
154 this.monthSelect.style.display='none';
\r
155 this.container.appendChild(this.monthSelect);
\r
157 // fix anchors so they work in IE6
\r
158 var a=this.container.getElementsByTagName('a');
\r
159 for (var i=0; i<a.length; i++)
\r
162 Event.observe(this.tbody,"click", this.saveAndClose.bindAsEventListener(this));
\r
163 Event.observe(this.tbody,"mouseover", this.mouseOver.bindAsEventListener(this));
164 Event.observe(this.tbody,"mouseout", this.mouseOut.bindAsEventListener(this));
165 document.getElementsByTagName("body")[0].appendChild(this.container);
\r
166 this.setDiv(this.container);
\r
168 this.bPageLoaded=true
\r
171 selectNow : function() {
\r
172 this.monthSelected=this.monthNow;
\r
173 this.yearSelected=this.yearNow;
\r
174 this.constructCalendar();
\r
177 createNavArrow: function(funcname,gifname) {
\r
178 var img=document.createElement("img");
\r
179 img.src=Rico.imgDir+gifname+'.gif';
\r
181 Event.observe(img,"click", this[funcname].bindAsEventListener(this), false);
\r
182 Event.observe(img,"mousedown", this.mouseDown.bindAsEventListener(this), false);
\r
183 Event.observe(img,"mouseup", this.mouseUp.bindAsEventListener(this), false);
\r
184 Event.observe(img,"mouseout", this.mouseUp.bindAsEventListener(this), false);
\r
188 mouseOver: function(e) {
\r
189 var el=Event.element(e);
\r
190 if (this.lastHighlight==el) return;
\r
191 this.unhighlight();
\r
192 var s=el.innerHTML.replace(/ /g,'');
\r
193 if (s=='' || el.className=='ricoCalWeekNum') return;
\r
194 var day=parseInt(s);
\r
195 if (isNaN(day)) return;
\r
196 this.lastHighlight=el;
\r
197 this.tmpColor=el.style.backgroundColor;
\r
198 el.style.backgroundColor=this.options.cursorColor;
\r
201 unhighlight: function() {
\r
202 if (!this.lastHighlight) return;
\r
203 this.lastHighlight.style.backgroundColor=this.tmpColor;
\r
204 this.lastHighlight=null;
\r
207 mouseOut: function(e) {
\r
208 var el=Event.element(e);
\r
209 if (el==this.lastHighlight) this.unhighlight();
\r
212 mouseDown: function(e) {
\r
213 var el=Event.element(e);
\r
214 this.repeatFunc=this[el.name].bind(this);
\r
215 this.timeoutID=setTimeout(this.repeatStart.bind(this),500);
\r
218 mouseUp: function(e) {
\r
219 clearTimeout(this.timeoutID);
\r
220 clearInterval(this.intervalID)
\r
223 repeatStart : function() {
\r
224 clearInterval(this.intervalID);
\r
225 this.intervalID=setInterval(this.repeatFunc,this.options.repeatInterval);
\r
228 // is yr/mo within minDate/MaxDate?
\r
229 isValidMonth : function(yr,mo) {
\r
230 if (yr < this.options.minDate.getFullYear()) return false;
\r
231 if (yr == this.options.minDate.getFullYear() && mo < this.options.minDate.getMonth()) return false;
\r
232 if (yr > this.options.maxDate.getFullYear()) return false;
\r
233 if (yr == this.options.maxDate.getFullYear() && mo > this.options.maxDate.getMonth()) return false;
\r
237 incMonth : function() {
\r
238 var newMonth=this.monthSelected+1;
\r
239 var newYear=this.yearSelected;
\r
244 if (!this.isValidMonth(newYear,newMonth)) return;
\r
245 this.monthSelected=newMonth;
\r
246 this.yearSelected=newYear;
\r
247 this.constructCalendar()
\r
250 decMonth : function() {
\r
251 var newMonth=this.monthSelected-1;
\r
252 var newYear=this.yearSelected;
\r
257 if (!this.isValidMonth(newYear,newMonth)) return;
\r
258 this.monthSelected=newMonth;
\r
259 this.yearSelected=newYear;
\r
260 this.constructCalendar()
\r
263 selectMonth : function(e) {
\r
264 var el=Event.element(e);
\r
265 this.monthSelected=parseInt(el.name);
\r
266 this.constructCalendar();
\r
270 popUpMonth : function() {
\r
271 this.monthSelect.style.display=this.monthSelect.style.display=='none' ? 'block' : 'none';
\r
274 popDownMonth : function() {
\r
275 this.monthSelect.style.display='none';
\r
278 /*** Year Pulldown ***/
\r
280 popUpYear : function() {
\r
281 var newYear=prompt(RicoTranslate.getPhrase("Year ("+this.options.minDate.getFullYear()+"-"+this.options.maxDate.getFullYear()+")"),this.yearSelected);
\r
282 if (newYear==null) return;
\r
283 newYear=parseInt(newYear);
\r
284 if (isNaN(newYear) || newYear<this.options.minDate.getFullYear() || newYear>this.options.maxDate.getFullYear()) {
\r
285 alert(RicoTranslate.getPhrase("Invalid year"));
\r
287 this.yearSelected=newYear;
\r
288 this.constructCalendar();
\r
292 incYear : function() {
\r
293 if (this.yearSelected>=this.options.maxDate.getFullYear()) return;
\r
294 this.yearSelected++;
\r
295 this.constructCalendar();
\r
298 decYear : function() {
\r
299 if (this.yearSelected<=this.options.minDate.getFullYear()) return;
\r
300 this.yearSelected--;
\r
301 this.constructCalendar();
\r
304 // tried a number of different week number functions posted on the net
\r
305 // this is the only one that produced consistent results when comparing week numbers for December and the following January
\r
306 WeekNbr : function(year,month,day) {
\r
307 var when = new Date(year,month,day);
\r
308 var newYear = new Date(year,0,1);
\r
309 var offset = 7 + 1 - newYear.getDay();
\r
310 if (offset == 8) offset = 1;
\r
311 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;
\r
312 var weeknum = Math.floor((daynum-offset+7)/7);
\r
313 if (weeknum == 0) {
\r
315 var prevNewYear = new Date(year,0,1);
\r
316 var prevOffset = 7 + 1 - prevNewYear.getDay();
\r
317 if (prevOffset == 2 || prevOffset == 8) weeknum = 53; else weeknum = 52;
\r
322 constructCalendar : function() {
\r
323 var aNumDays = Array (31,0,31,30,31,30,31,31,30,31,30,31)
\r
324 var startDate = new Date (this.yearSelected,this.monthSelected,1)
\r
325 var endDate,numDaysInMonth
\r
327 if (typeof this.monthSelected!='number' || this.monthSelected>=12 || this.monthSelected<0) {
\r
328 alert('ERROR in calendar: monthSelected='+this.monthSelected);
\r
331 var today = new Date();
\r
332 this.dateNow = today.getDate();
\r
333 this.monthNow = today.getMonth();
\r
334 this.yearNow = today.getFullYear();
\r
336 if (this.monthSelected==1) {
\r
337 endDate = new Date (this.yearSelected,this.monthSelected+1,1);
\r
338 endDate = new Date (endDate - (24*60*60*1000));
\r
339 numDaysInMonth = endDate.getDate()
\r
341 numDaysInMonth = aNumDays[this.monthSelected];
\r
343 var dayPointer = startDate.getDay() - this.options.startAt
\r
344 if (dayPointer<0) dayPointer+=7;
\r
345 this.popDownMonth();
\r
347 this.bgcolor=Element.getStyle(this.tbody,'background-color');
\r
348 this.bgcolor=this.bgcolor.replace(/\"/g,'');
\r
349 if (this.options.showWeekNumber) {
\r
350 for (var i=1; i<7; i++)
\r
351 this.tbody.rows[i].cells[0].innerHTML=' ';
\r
353 for ( var i=1; i<=dayPointer; i++ )
\r
354 this.resetCell(this.tbody.rows[1].cells[i]);
\r
356 for ( var datePointer=1,r=1; datePointer<=numDaysInMonth; datePointer++,dayPointer++ ) {
\r
357 var colnum=dayPointer % 7 + 1;
\r
358 if (this.options.showWeekNumber==1 && colnum==1)
\r
359 this.tbody.rows[r].cells[0].innerHTML=this.WeekNbr(this.yearSelected,this.monthSelected,datePointer);
\r
360 var dateClass=this.styles[colnum];
\r
361 if ((datePointer==this.dateNow)&&(this.monthSelected==this.monthNow)&&(this.yearSelected==this.yearNow))
\r
362 dateClass='ricoCalToday';
\r
363 var c=this.tbody.rows[r].cells[colnum];
\r
364 c.innerHTML=" " + datePointer + " ";
\r
365 c.className=dateClass;
\r
366 var bordercolor=(datePointer==this.odateSelected) && (this.monthSelected==this.omonthSelected) && (this.yearSelected==this.oyearSelected) ? this.options.selectedDateBorder : this.bgcolor;
\r
367 c.style.border='1px solid '+bordercolor;
\r
368 var h=this.Holidays[this.holidayKey(this.yearSelected,this.monthSelected,datePointer)];
\r
369 if (!h) h=this.Holidays[this.holidayKey(0,this.monthSelected,datePointer)];
\r
370 c.style.color=h ? h.txtColor : '';
\r
371 c.style.backgroundColor=h ? h.bgColor : '';
\r
372 c.title=h ? h.desc : '';
\r
373 if (colnum==7) r++;
\r
375 while (dayPointer<42) {
\r
376 var colnum=dayPointer % 7 + 1;
\r
377 this.resetCell(this.tbody.rows[r].cells[colnum]);
\r
379 if (colnum==7) r++;
\r
382 this.titleMonth.innerHTML = RicoTranslate.monthNames[this.monthSelected].substring(0,3);
\r
383 this.titleYear.innerHTML = this.yearSelected;
\r
384 if (this.options.showToday)
\r
385 this.todayCell.innerHTML=this.todayString+'<span>'+this.dateNow + " " + RicoTranslate.monthNames[this.monthNow].substring(0,3) + " " + this.yearNow+'</span>';
\r
386 this.monthSelect.style.top=this.thead.offsetHeight+'px';
\r
387 this.monthSelect.style.left=this.titleMonth.offsetLeft+'px';
\r
390 resetCell: function(c) {
\r
391 c.innerHTML=" ";
\r
392 c.className='ricoCalEmpty';
\r
393 c.style.border='1px solid '+this.bgcolor;
\r
395 c.style.backgroundColor='';
\r
399 saveAndClose : function(e) {
\r
401 var el=Event.element(e);
\r
402 var s=el.innerHTML.replace(/ /g,'');
\r
403 if (s=='' || el.className=='ricoCalWeekNum') return;
\r
404 var day=parseInt(s);
\r
405 if (isNaN(day)) return;
\r
406 var d=new Date(this.yearSelected,this.monthSelected,day);
\r
407 var dateStr=d.formatDate(this.options.dateFmt=='ISO8601' ? 'yyyy-mm-dd' : this.options.dateFmt);
\r
408 if (this.returnValue) this.returnValue(dateStr);
\r
412 open : function(curval) {
\r
413 if (!this.bPageLoaded) return;
\r
414 if (typeof curval=='object') {
\r
415 this.dateSelected = curval.getDate();
\r
416 this.monthSelected = curval.getMonth();
\r
417 this.yearSelected = curval.getFullYear();
\r
418 } else if (this.options.dateFmt=='ISO8601') {
\r
420 d.setISO8601(curval);
\r
421 this.dateSelected = d.getDate();
\r
422 this.monthSelected = d.getMonth();
\r
423 this.yearSelected = d.getFullYear();
\r
424 } else if (this.re.exec(curval)) {
\r
425 var aDate=new Array(RegExp.$1,RegExp.$3,RegExp.$5);
\r
426 this.dateSelected = parseInt(aDate[this.dateParts['dd']], 10);
\r
427 this.monthSelected = parseInt(aDate[this.dateParts['mm']], 10) - 1;
\r
428 this.yearSelected = parseInt(aDate[this.dateParts['yyyy']], 10);
\r
430 if (curval) alert('ERROR: invalid date passed to calendar ('+curval+')');
\r
431 this.dateSelected = this.dateNow
\r
432 this.monthSelected = this.monthNow
\r
433 this.yearSelected = this.yearNow
\r
435 this.odateSelected=this.dateSelected
\r
436 this.omonthSelected=this.monthSelected
\r
437 this.oyearSelected=this.yearSelected
\r
438 this.constructCalendar();
\r
443 Rico.includeLoaded('ricoCalendar.js');