// // calendar -- a javascript date picker designed for easy localization. // // $Id$ // // // Author: Per Norrman (pernorrman@telia.com) // // Based on Tapestry 2.3-beta1 Datepicker by Paul Geerts // // Thanks to: // Vladimir [vyc@quorus-ms.ru] for fixing the IE6 zIndex problem. // // The normal setup would be to have one text field for displaying the // selected date, and one button to show/hide the date picker control. // This is the recommended javascript code: // // // // The init function is invoked when the body is loaded. // // function Calendar(date) { if (arguments.length == 0) { this._currentDate = new Date(); this._selectedDate = null; } else { this._currentDate = new Date(date); this._selectedDate = new Date(date); } // Accumulated days per month, for normal and for leap years. // Used in week number calculations. Calendar.NUM_DAYS = [0,31,59,90,120,151,181,212,243,273,304,334]; Calendar.LEAP_NUM_DAYS = [0,31,60,91,121,152,182,213,244,274,305,335]; this._bw = new bw_check(); this._showing = false; this._includeWeek = false; this._hideOnSelect = true; this._alwaysVisible = false; this._dateSlot = new Array(42); this._weekSlot = new Array(6); this._firstDayOfWeek = 1; this._minimalDaysInFirstWeek = 4; this._monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; this._shortMonthNames = [ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" ]; // Week days start with Sunday=0, ... Saturday=6 this._weekDayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; this._shortWeekDayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ]; this._defaultFormat = "yyyy-MM-dd"; this._format = this._defaultFormat; this._calDiv = null; } /** * CREATE the Calendar DOM element */ Calendar.prototype.create = function() { var div; var table; var tbody; var tr; var td; var dp = this; // Create the top-level div element this._calDiv = document.createElement("div"); this._calDiv.className = "calendar"; this._calDiv.style.position = "absolute"; this._calDiv.style.display = "none"; this._calDiv.style.border = "1px solid WindowText"; this._calDiv.style.textAlign = "center"; this._calDiv.style.background = "Window"; this._calDiv.style.zIndex = "400"; // header div div = document.createElement("div"); div.className = "calendarHeader"; div.style.background = "ActiveCaption"; div.style.padding = "3px"; div.style.borderBottom = "1px solid WindowText"; this._calDiv.appendChild(div); table = document.createElement("table"); table.style.cellSpacing = 0; div.appendChild(table); tbody = document.createElement("tbody"); table.appendChild(tbody); tr = document.createElement("tr"); tbody.appendChild(tr); // Previous Month Button td = document.createElement("td"); this._previousMonth = document.createElement("button"); this._previousMonth.className = "prevMonthButton" this._previousMonth.appendChild(document.createTextNode("<<")); //this._previousMonth.appendChild(document.createTextNode(String.fromCharCode(9668))); td.appendChild(this._previousMonth); tr.appendChild(td); // // Create the month drop down // td = document.createElement("td"); td.className = "labelContainer"; tr.appendChild(td); this._monthSelect = document.createElement("select"); for (var i = 0 ; i < this._monthNames.length ; i++) { var opt = document.createElement("option"); opt.innerHTML = this._monthNames[i]; opt.value = i; if (i == this._currentDate.getMonth()) { opt.selected = true; } this._monthSelect.appendChild(opt); } td.appendChild(this._monthSelect); // // Create the year drop down // td = document.createElement("td"); td.className = "labelContainer"; tr.appendChild(td); this._yearSelect = document.createElement("select"); for(var i=1920; i < 2050; ++i) { var opt = document.createElement("option"); opt.innerHTML = i; opt.value = i; if (i == this._currentDate.getFullYear()) { opt.selected = false; } this._yearSelect.appendChild(opt); } td.appendChild(this._yearSelect); td = document.createElement("td"); this._nextMonth = document.createElement("button"); this._nextMonth.appendChild(document.createTextNode(">>")); //this._nextMonth.appendChild(document.createTextNode(String.fromCharCode(9654))); this._nextMonth.className = "nextMonthButton"; td.appendChild(this._nextMonth); tr.appendChild(td); // Calendar body div = document.createElement("div"); div.className = "calendarBody"; this._calDiv.appendChild(div); this._table = div; // Create the inside of calendar body var text; table = document.createElement("table"); //table.style.width="100%"; table.className = "grid"; table.style.font = "small-caption"; table.style.fontWeight = "normal"; table.style.textAalign = "center"; table.style.color = "WindowText"; table.style.cursor = "default"; table.cellPadding = "3"; table.cellSpacing = "0"; div.appendChild(table); var thead = document.createElement("thead"); table.appendChild(thead); tr = document.createElement("tr"); thead.appendChild(tr); // weekdays header if (this._includeWeek) { td = document.createElement("th"); text = document.createTextNode("w"); td.appendChild(text); td.className = "weekNumberHead"; td.style.textAlign = "left"; tr.appendChild(td); } for(i=0; i < 7; ++i) { td = document.createElement("th"); text = document.createTextNode(this._shortWeekDayNames[(i+this._firstDayOfWeek)%7]); td.appendChild(text); td.className = "weekDayHead"; td.style.fontWeight = "bold"; td.style.borderBottom = "1px solid WindowText"; tr.appendChild(td); } // Date grid tbody = document.createElement("tbody"); table.appendChild(tbody); for(week=0; week<6; ++week) { tr = document.createElement("tr"); tbody.appendChild(tr); if (this._includeWeek) { td = document.createElement("td"); td.className = "weekNumber"; td.style.fontWeight = "normal"; td.style.borderRight = "1px solid WindowText"; td.style.textAlign = "left"; text = document.createTextNode(String.fromCharCode(160)); td.appendChild(text); //setCursor(td); td.align="center"; tr.appendChild(td); var tmp = new Object(); tmp.tag = "WEEK"; tmp.value = -1; tmp.data = text; this._weekSlot[week] = tmp; } for(day=0; day<7; ++day) { td = document.createElement("td"); text = document.createTextNode(String.fromCharCode(160)); td.appendChild(text); setCursor(td); td.align="center"; td.style.fontWeight="normal"; tr.appendChild(td); var tmp = new Object(); tmp.tag = "DATE"; tmp.value = -1; tmp.data = text; this._dateSlot[(week*7)+day] = tmp; } } // Calendar Footer div = document.createElement("div"); div.className = "calendarFooter"; this._calDiv.appendChild(div); table = document.createElement("table"); //table.style.width="100%"; table.className = "footerTable"; table.cellSpacing = 0; div.appendChild(table); tbody = document.createElement("tbody"); table.appendChild(tbody); tr = document.createElement("tr"); tbody.appendChild(tr); // // The TODAY button // td = document.createElement("td"); this._todayButton = document.createElement("button"); var today = new Date(); var buttonText = today.getDate() + " " + this._monthNames[today.getMonth()] + ", " + today.getFullYear(); this._todayButton.appendChild(document.createTextNode(buttonText)); td.appendChild(this._todayButton); tr.appendChild(td); // // The CLEAR button // td = document.createElement("td"); this._clearButton = document.createElement("button"); var today = new Date(); buttonText = "Clear"; this._clearButton.appendChild(document.createTextNode(buttonText)); td.appendChild(this._clearButton); tr.appendChild(td); this._update(); this._updateHeader(); // IE55+ extension this._previousMonth.hideFocus = true; this._nextMonth.hideFocus = true; this._todayButton.hideFocus = true; // end IE55+ extension // hook up events // buttons this._previousMonth.onclick = function () { dp.prevMonth(); }; this._nextMonth.onclick = function () { dp.nextMonth(); }; this._todayButton.onclick = function () { dp.setSelectedDate(new Date()); dp.hide(); }; this._clearButton.onclick = function () { dp.clearSelectedDate(); dp.hide(); }; this._calDiv.onselectstart = function () { return false; }; this._table.onclick = function (e) { // find event if (e == null) e = document.parentWindow.event; // find td var el = e.target != null ? e.target : e.srcElement; while (el.nodeType != 1) el = el.parentNode; while (el != null && el.tagName && el.tagName.toLowerCase() != "td") el = el.parentNode; // if no td found, return if (el == null || el.tagName == null || el.tagName.toLowerCase() != "td") return; var d = new Date(dp._currentDate); var n = Number(el.firstChild.data); if (isNaN(n) || n <= 0 || n == null) return; if (el.className == "weekNumber") return; d.setDate(n); dp.setSelectedDate(d); if (!dp._alwaysVisible && dp._hideOnSelect) { dp.hide(); } }; this._calDiv.onkeydown = function (e) { if (e == null) e = document.parentWindow.event; var kc = e.keyCode != null ? e.keyCode : e.charCode; if(kc == 13) { var d = new Date(dp._currentDate).valueOf(); dp.setSelectedDate(d); if (!dp._alwaysVisible && dp._hideOnSelect) { dp.hide(); } return false; } if (kc < 37 || kc > 40) return true; var d = new Date(dp._currentDate).valueOf(); if (kc == 37) // left d -= 24 * 60 * 60 * 1000; else if (kc == 39) // right d += 24 * 60 * 60 * 1000; else if (kc == 38) // up d -= 7 * 24 * 60 * 60 * 1000; else if (kc == 40) // down d += 7 * 24 * 60 * 60 * 1000; dp.setCurrentDate(new Date(d)); return false; } // ie6 extension this._calDiv.onmousewheel = function (e) { if (e == null) e = document.parentWindow.event; var n = - e.wheelDelta / 120; var d = new Date(dp._currentDate); var m = d.getMonth() + n; d.setMonth(m); dp.setCurrentDate(d); return false; } this._monthSelect.onchange = function(e) { if (e == null) e = document.parentWindow.event; e = getEventObject(e); dp.setMonth(e.value); } this._monthSelect.onclick = function(e) { if (e == null) e = document.parentWindow.event; e = getEventObject(e); e.cancelBubble = true; } this._yearSelect.onchange = function(e) { if (e == null) e = document.parentWindow.event; e = getEventObject(e); dp.setYear(e.value); } document.body.appendChild(this._calDiv); return this._calDiv; } Calendar.prototype._update = function() { // Calculate the number of days in the month for the selected date var date = this._currentDate; var today = toISODate(new Date()); var selected = ""; if (this._selectedDate != null) { selected = toISODate(this._selectedDate); } var current = toISODate(this._currentDate); var d1 = new Date(date.getFullYear(), date.getMonth(), 1); var d2 = new Date(date.getFullYear(), date.getMonth()+1, 1); var monthLength = Math.round((d2 - d1) / (24 * 60 * 60 * 1000)); // Find out the weekDay index for the first of this month var firstIndex = (d1.getDay() - this._firstDayOfWeek) % 7 ; if (firstIndex < 0) { firstIndex += 7; } var index = 0; while (index < firstIndex) { this._dateSlot[index].value = -1; this._dateSlot[index].data.data = String.fromCharCode(160); this._dateSlot[index].data.parentNode.className = ""; this._dateSlot[index].data.parentNode.style.fontWeight = "normal"; this._dateSlot[index].data.parentNode.style.border= "none"; index++; } for (i = 1; i <= monthLength; i++, index++) { this._dateSlot[index].value = i; this._dateSlot[index].data.data = i; this._dateSlot[index].data.parentNode.className = ""; this._dateSlot[index].data.parentNode.style.fontWeight = "normal"; this._dateSlot[index].data.parentNode.style.border= "none"; if (toISODate(d1) == today) { this._dateSlot[index].data.parentNode.className = "today"; this._dateSlot[index].data.parentNode.style.fontWeight = "bold"; } if (toISODate(d1) == current) { this._dateSlot[index].data.parentNode.className += " current"; this._dateSlot[index].data.parentNode.style.border= "1px dotted WindowText"; } if (toISODate(d1) == selected) { this._dateSlot[index].data.parentNode.className += " selected"; this._dateSlot[index].data.parentNode.style.border= "1px solid WindowText"; } d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+1); } var lastDateIndex = index; while(index < 42) { this._dateSlot[index].value = -1; this._dateSlot[index].data.data = String.fromCharCode(160); this._dateSlot[index].data.parentNode.className = ""; this._dateSlot[index].data.parentNode.style.fontWeight = "normal"; this._dateSlot[index].data.parentNode.style.border= "none"; ++index; } // Week numbers if (this._includeWeek) { d1 = new Date(date.getFullYear(), date.getMonth(), 1); for (i=0; i < 6; ++i) { if (i == 5 && lastDateIndex < 36) { this._weekSlot[i].data.data = String.fromCharCode(160); this._weekSlot[i].data.parentNode.style.borderRight = "none"; } else { week = weekNumber(this, d1); this._weekSlot[i].data.data = week; this._weekSlot[i].data.parentNode.style.borderRight = "1px solid WindowText"; } d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+7); } } } Calendar.prototype.show = function(element) { if(!this._showing) { var p = getPoint(element); this._calDiv.style.display = "block"; this._calDiv.style.top = (p.y + element.offsetHeight + 1) + "px"; this._calDiv.style.left = p.x + "px"; this._showing = true; /* -------- */ if( this._bw.ie6 ) { dw = this._calDiv.offsetWidth; dh = this._calDiv.offsetHeight; var els = document.getElementsByTagName("body"); var body = els[0]; if( !body ) return; //paste iframe under the modal var underDiv = this._calDiv.cloneNode(false); underDiv.style.zIndex="390"; underDiv.style.margin = "0px"; underDiv.style.padding = "0px"; underDiv.style.display = "block"; underDiv.style.width = dw; underDiv.style.height = dh; underDiv.style.border = "1px solid WindowText"; underDiv.innerHTML = ""; body.appendChild(underDiv); this._underDiv = underDiv; } /* -------- */ this._calDiv.focus(); } }; Calendar.prototype.hide = function() { if(this._showing) { this._calDiv.style.display = "none"; this._showing = false; if( this._bw.ie6 ) { if( this._underDiv ) this._underDiv.removeNode(true); } } } Calendar.prototype.toggle = function(element) { if(this._showing) { this.hide(); } else { this.show(element); } } Calendar.prototype.onchange = function() {}; Calendar.prototype.setCurrentDate = function(date) { if (date == null) { return; } // if string or number create a Date object if (typeof date == "string" || typeof date == "number") { date = new Date(date); } // do not update if not really changed if (this._currentDate.getDate() != date.getDate() || this._currentDate.getMonth() != date.getMonth() || this._currentDate.getFullYear() != date.getFullYear()) { this._currentDate = new Date(date); this._updateHeader(); this._update(); } } Calendar.prototype.setSelectedDate = function(date) { this._selectedDate = new Date(date); this.setCurrentDate(this._selectedDate); if (typeof this.onchange == "function") { this.onchange(); } } Calendar.prototype.clearSelectedDate = function() { this._selectedDate = null; if (typeof this.onchange == "function") { this.onchange(); } } Calendar.prototype.getElement = function() { return this._calDiv; } Calendar.prototype.setIncludeWeek = function(v) { if (this._calDiv == null) { this._includeWeek = v; } } Calendar.prototype.getSelectedDate = function () { if (this._selectedDate == null) { return null; } else { return new Date(this._selectedDate); } } Calendar.prototype._updateHeader = function () { // var options = this._monthSelect.options; var m = this._currentDate.getMonth(); for(var i=0; i < options.length; ++i) { options[i].selected = false; if (options[i].value == m) { options[i].selected = true; } } options = this._yearSelect.options; var year = this._currentDate.getFullYear(); for(var i=0; i < options.length; ++i) { options[i].selected = false; if (options[i].value == year) { options[i].selected = true; } } } Calendar.prototype.setYear = function(year) { var d = new Date(this._currentDate); d.setFullYear(year); this.setCurrentDate(d); } Calendar.prototype.setMonth = function (month) { var d = new Date(this._currentDate); d.setMonth(month); this.setCurrentDate(d); } Calendar.prototype.nextMonth = function () { this.setMonth(this._currentDate.getMonth()+1); } Calendar.prototype.prevMonth = function () { this.setMonth(this._currentDate.getMonth()-1); } Calendar.prototype.setFirstDayOfWeek = function (nFirstWeekDay) { this._firstDayOfWeek = nFirstWeekDay; } Calendar.prototype.getFirstDayOfWeek = function () { return this._firstDayOfWeek; } Calendar.prototype.setMinimalDaysInFirstWeek = function(n) { this._minimalDaysInFirstWeek = n; } Calendar.prototype.getMinimalDaysInFirstWeek = function () { return this._minimalDaysInFirstWeek; } Calendar.prototype.setMonthNames = function(a) { // sanity test this._monthNames = a; } Calendar.prototype.setShortMonthNames = function(a) { // sanity test this._shortMonthNames = a; } Calendar.prototype.setWeekDayNames = function(a) { // sanity test this._weekDayNames = a; } Calendar.prototype.setShortWeekDayNames = function(a) { // sanity test this._shortWeekDayNames = a; } Calendar.prototype.getFormat = function() { return this._format; } Calendar.prototype.setFormat = function(f) { this._format = f; } Calendar.prototype.formatDate = function() { if (this._selectedDate == null) { return ""; } var bits = new Array(); // work out what each bit should be var date = this._selectedDate; bits['d'] = date.getDate(); bits['dd'] = pad(date.getDate(),2); bits['ddd'] = this._shortWeekDayNames[date.getDay()]; bits['dddd'] = this._weekDayNames[date.getDay()]; bits['M'] = date.getMonth()+1; bits['MM'] = pad(date.getMonth()+1,2); bits['MMM'] = this._shortMonthNames[date.getMonth()]; bits['MMMM'] = this._monthNames[date.getMonth()]; var yearStr = "" + date.getFullYear(); yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr; bits['yyyy'] = yearStr; bits['yy'] = bits['yyyy'].toString().substr(2,2); // do some funky regexs to replace the format string // with the real values var frm = new String(this._format); var sect; for (sect in bits) { frm = eval("frm.replace(/\\b" + sect + "\\b/,'" + bits[sect] + "');"); } return frm; } function isLeapYear(year) { return ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))); } function yearLength(year) { if (isLeapYear(year)) return 366; else return 365; } function dayOfYear(date) { var a = Calendar.NUM_DAYS; if (isLeapYear(date.getFullYear())) { a = Calendar.LEAP_NUM_DAYS; } var month = date.getMonth(); return a[month] + date.getDate(); } // --------------------------------------------- // Week number stuff // --------------------------------------------- function weekNumber(cal, date) { var dow = date.getDay(); var doy = dayOfYear(date); var year = date.getFullYear(); // Compute the week of the year. Valid week numbers run from 1 to 52 // or 53, depending on the year, the first day of the week, and the // minimal days in the first week. Days at the start of the year may // fall into the last week of the previous year; days at the end of // the year may fall into the first week of the next year. var relDow = (dow + 7 - cal.getFirstDayOfWeek()) % 7; // 0..6 var relDowJan1 = (dow - doy + 701 - cal.getFirstDayOfWeek()) % 7; // 0..6 var week = Math.floor((doy - 1 + relDowJan1) / 7); // 0..53 if ((7 - relDowJan1) >= cal.getMinimalDaysInFirstWeek()) { ++week; } if (doy > 359) { // Fast check which eliminates most cases // Check to see if we are in the last week; if so, we need // to handle the case in which we are the first week of the // next year. var lastDoy = yearLength(year); var lastRelDow = (relDow + lastDoy - doy) % 7; if (lastRelDow < 0) { lastRelDow += 7; } if (((6 - lastRelDow) >= cal.getMinimalDaysInFirstWeek()) && ((doy + 7 - relDow) > lastDoy)) { week = 1; } } else if (week == 0) { // We are the last week of the previous year. var prevDoy = doy + yearLength(year - 1); week = weekOfPeriod(cal, prevDoy, dow); } return week; } function weekOfPeriod(cal, dayOfPeriod, dayOfWeek) { // Determine the day of the week of the first day of the period // in question (either a year or a month). Zero represents the // first day of the week on this calendar. var periodStartDayOfWeek = (dayOfWeek - cal.getFirstDayOfWeek() - dayOfPeriod + 1) % 7; if (periodStartDayOfWeek < 0) { periodStartDayOfWeek += 7; } // Compute the week number. Initially, ignore the first week, which // may be fractional (or may not be). We add periodStartDayOfWeek in // order to fill out the first week, if it is fractional. var weekNo = Math.floor((dayOfPeriod + periodStartDayOfWeek - 1) / 7); // If the first week is long enough, then count it. If // the minimal days in the first week is one, or if the period start // is zero, we always increment weekNo. if ((7 - periodStartDayOfWeek) >= cal.getMinimalDaysInFirstWeek()) { ++weekNo; } return weekNo; } function getEventObject(e) { // utility function to retrieve object from event if (navigator.appName == "Microsoft Internet Explorer") { return e.srcElement; } else { // is mozilla/netscape // need to crawl up the tree to get the first "real" element // i.e. a tag, not raw text var o = e.target; while (!o.tagName) { o = o.parentNode; } return o; } } function addEvent(name, obj, funct) { // utility function to add event handlers if (navigator.appName == "Microsoft Internet Explorer") { obj.attachEvent("on"+name, funct); } else { // is mozilla/netscape obj.addEventListener(name, funct, false); } } function deleteEvent(name, obj, funct) { // utility function to delete event handlers if (navigator.appName == "Microsoft Internet Explorer") { obj.detachEvent("on"+name, funct); } else { // is mozilla/netscape obj.removeEventListener(name, funct, false); } } function setCursor(obj) { if (navigator.appName == "Microsoft Internet Explorer") { obj.style.cursor = "hand"; } else { // is mozilla/netscape obj.style.cursor = "pointer"; } } function Point(iX, iY) { this.x = iX; this.y = iY; } function getPoint(aTag) { var oTmp = aTag; var point = new Point(0,0); do { point.x += oTmp.offsetLeft; point.y += oTmp.offsetTop; oTmp = oTmp.offsetParent; } while (oTmp.tagName != "BODY"); return point; } function toISODate(date) { var s = date.getFullYear(); var m = date.getMonth() + 1; if (m < 10) { m = "0" + m; } var day = date.getDate(); if (day < 10) { day = "0" + day; } return String(s) + String(m) + String(day); } function pad(number,X) { // utility function to pad a number to a given width X = (!X ? 2 : X); number = ""+number; while (number.length < X) { number = "0" + number; } return number; } function bw_check() { var is_major = parseInt( navigator.appVersion ); this.nver = is_major; this.ver = navigator.appVersion; this.agent = navigator.userAgent; this.dom = document.getElementById ? 1 : 0; this.opera = window.opera ? 1 : 0; this.ie5 = ( this.ver.indexOf( "MSIE 5" ) > -1 && this.dom && !this.opera ) ? 1 : 0; this.ie6 = ( this.ver.indexOf( "MSIE 6" ) > -1 && this.dom && !this.opera ) ? 1 : 0; this.ie4 = ( document.all && !this.dom && !this.opera ) ? 1 : 0; this.ie = this.ie4 || this.ie5 || this.ie6; this.mac = this.agent.indexOf( "Mac" ) > -1; this.ns6 = ( this.dom && parseInt( this.ver ) >= 5 ) ? 1 : 0; this.ie3 = ( this.ver.indexOf( "MSIE" ) && ( is_major < 4 ) ); this.hotjava = ( this.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0; this.ns4 = ( document.layers && !this.dom && !this.hotjava ) ? 1 : 0; this.bw = ( this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera ); this.ver3 = ( this.hotjava || this.ie3 ); this.opera7 = ( ( this.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( this.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) ); this.operaOld = this.opera && !this.opera7; return this; };