/*----------------------------------------------------------------------------\ | |
| Date Picker 1.06 | | |
|-----------------------------------------------------------------------------| | |
| Created by Erik Arvidsson | | |
| (http://webfx.eae.net/contact.html#erik) | | |
| For WebFX (http://webfx.eae.net/) | | |
|-----------------------------------------------------------------------------| | |
| A DOM based Date Picker | | |
|-----------------------------------------------------------------------------| | |
| Copyright (c) 1999, 2002, 2002, 2003, 2004, 2006 Erik Arvidsson | | |
|-----------------------------------------------------------------------------| | |
| Licensed under the Apache License, Version 2.0 (the "License"); you may not | | |
| use this file except in compliance with the License. You may obtain a copy | | |
| of the License at http://www.apache.org/licenses/LICENSE-2.0 | | |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | |
| Unless required by applicable law or agreed to in writing, software | | |
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | | |
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | | |
| License for the specific language governing permissions and limitations | | |
| under the License. | | |
|-----------------------------------------------------------------------------| | |
| Dependencies: datepicker.css Date picker style declarations | | |
|-----------------------------------------------------------------------------| | |
| 2002-02-10 | Changed _update method to only update the text nodes instead | | |
| | rewriting the entire table. Also added support for mouse wheel | | |
| | in IE6. | | |
| 2002-01-14 | Cleaned up for 1.0 public version | | |
| 2002-01-15 | Replace all innerHTML calls with DOM1 methods | | |
| 2002-01-18 | Minor IE6 bug that occured when dragging the mouse | | |
| 2002-01-19 | Added a popup that is shown when the user clicks on the month. | | |
| | This allows navigation to 6 adjacent months. | | |
| 2002-04-10 | Fixed a bug that occured in the popup when a date was selected | | |
| | that caused surroundung months to "overflow" | | |
| | This had the effect that one could get two October months | | |
| | listed. | | |
| 2002-09-06 | I had missed one place were window was used instead of | | |
| | doc.parentWindow | | |
| 2003-08-28 | Added support for ensurin no date overflow when changing | | |
| | months. | | |
| 2004-01-10 | Adding type on the buttons to ensure they are not submit | | |
| | buttons. Minor CSS change for CSS2 | | |
| 2006-05-28 | Changed license to Apache Software License 2.0. | | |
|-----------------------------------------------------------------------------| | |
| Created 2001-10-?? | All changes are in the log above. | Updated 2006-05-28 | | |
\----------------------------------------------------------------------------*/ | |
// The DatePicker constructor | |
// oDate : Date Optional argument representing the date to select | |
// Note: some minor modifications for Tapestry, to work well as a popup. | |
function DatePicker(oDate) | |
{ | |
// check arguments | |
if (arguments.length == 0) | |
{ | |
this._selectedDate = new Date; | |
this._none = false; | |
} | |
else | |
{ | |
this._selectedDate = oDate || new Date(); | |
this._none = oDate == null; | |
} | |
this._matrix = [[],[],[],[],[],[],[]]; | |
this._showNone = true; | |
this._showToday = true; | |
this._firstWeekDay = 0; // start week with monday according to standards | |
this._redWeekDay = 6; // sunday is the default red day. | |
this._dontChangeNone = false; | |
} | |
// two static fields describing the name of the months abd days | |
DatePicker.months = [ | |
"January", "February", "March", "April", | |
"May", "June", "July", "August", | |
"September", "October", "November", "December"]; | |
DatePicker.days = ["m", "t", "w", "t", "f", "s", "s"]; | |
// Function invoked whenever the selected date changes, whether by | |
// navigation or when the user selects a date. | |
DatePicker.prototype.onchange = function () | |
{ | |
}; | |
// onselect is more specified than onchange, and is triggered only when the user makes a specific selection | |
// using the calendar (rather than navigating to a new month). For Tapestry, | |
// this will dismiss the popup. | |
DatePicker.prototype.onselect = function() | |
{ | |
} | |
// create the nodes inside the date picker | |
DatePicker.prototype.create = function (doc) | |
{ | |
if (doc == null) doc = document; | |
this._document = doc; | |
// create elements | |
this._el = doc.createElement("div"); | |
this._el.className = "datePicker"; | |
// header | |
var div = doc.createElement("div"); | |
div.className = "header"; | |
this._el.appendChild(div); | |
var headerTable = doc.createElement("table"); | |
headerTable.className = "headerTable"; | |
headerTable.cellSpacing = 0; | |
div.appendChild(headerTable); | |
var tBody = doc.createElement("tbody"); | |
headerTable.appendChild(tBody); | |
var tr = doc.createElement("tr"); | |
tBody.appendChild(tr); | |
var td = doc.createElement("td"); | |
this._previousMonth = doc.createElement("button"); | |
this._previousMonth.className = "previousButton"; | |
this._previousMonth.setAttribute("type", "button"); | |
td.appendChild(this._previousMonth); | |
tr.appendChild(td); | |
td = doc.createElement("td"); | |
td.className = "labelContainer"; | |
tr.appendChild(td); | |
this._topLabel = doc.createElement("a"); | |
this._topLabel.className = "topLabel"; | |
this._topLabel.href = "#"; | |
this._topLabel.appendChild(doc.createTextNode(String.fromCharCode(160))); | |
td.appendChild(this._topLabel); | |
this._labelPopup = doc.createElement("div"); | |
this._labelPopup.className = "labelPopup"; | |
// no insertion | |
td = doc.createElement("td"); | |
this._nextMonth = doc.createElement("button"); | |
this._nextMonth.className = "nextButton"; | |
this._nextMonth.setAttribute("type", "button"); | |
td.appendChild(this._nextMonth); | |
tr.appendChild(td); | |
// grid | |
div = doc.createElement("div"); | |
div.className = "grid"; | |
this._el.appendChild(div); | |
this._table = div; | |
// footer | |
div = doc.createElement("div"); | |
div.className = "footer"; | |
this._el.appendChild(div); | |
var footerTable = doc.createElement("table"); | |
footerTable.className = "footerTable"; | |
footerTable.cellSpacing = 0; | |
div.appendChild(footerTable); | |
tBody = doc.createElement("tbody"); | |
footerTable.appendChild(tBody); | |
tr = doc.createElement("tr"); | |
tBody.appendChild(tr); | |
td = doc.createElement("td"); | |
this._todayButton = doc.createElement("button"); | |
this._todayButton.className = "todayButton"; | |
this._todayButton.setAttribute("type", "button"); | |
this._todayButton.appendChild(doc.createTextNode("Today")); | |
td.appendChild(this._todayButton); | |
tr.appendChild(td); | |
td = doc.createElement("td"); | |
td.className = "filler"; | |
td.appendChild(doc.createTextNode(String.fromCharCode(160))); | |
tr.appendChild(td); | |
td = doc.createElement("td"); | |
this._noneButton = doc.createElement("button"); | |
this._noneButton.className = "noneButton"; | |
this._noneButton.setAttribute("type", "button"); | |
this._noneButton.appendChild(doc.createTextNode("None")); | |
td.appendChild(this._noneButton); | |
tr.appendChild(td); | |
this._createTable(doc); | |
this._updateTable(); | |
this._setTopLabel(); | |
if (!this._showNone) | |
this._noneButton.style.visibility = "hidden"; | |
if (!this._showToday) | |
this._todayButton.style.visibility = "hidden"; | |
// IE55+ extension | |
this._previousMonth.hideFocus = true; | |
this._nextMonth.hideFocus = true; | |
this._todayButton.hideFocus = true; | |
this._noneButton.hideFocus = true; | |
// end IE55+ extension | |
// hook up events | |
var dp = this; | |
// buttons | |
this._previousMonth.onclick = function () | |
{ | |
dp._dontChangeNone = true; | |
dp.goToPreviousMonth(); | |
dp._dontChangeNone = false; | |
}; | |
this._nextMonth.onclick = function () | |
{ | |
dp._dontChangeNone = true; | |
dp.goToNextMonth(); | |
dp._dontChangeNone = false; | |
}; | |
this._todayButton.onclick = function () | |
{ | |
dp.goToToday(); | |
}; | |
this._noneButton.onclick = function () | |
{ | |
dp.setDate(null, true); | |
}; | |
this._el.onselectstart = function () | |
{ | |
return false; | |
}; | |
this._table.onclick = function (e) | |
{ | |
// find event | |
if (e == null) e = doc.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._selectedDate); | |
var n = Number(el.firstChild.data); | |
if (isNaN(n) || n <= 0 || n == null) | |
return; | |
d.setDate(n); | |
dp.setDate(d, true); | |
}; | |
// show popup | |
this._topLabel.onclick = function (e) | |
{ | |
dp._showLabelPopup(); | |
return false; | |
}; | |
this._el.onkeydown = function (e) | |
{ | |
if (e == null) e = doc.parentWindow.event; | |
var kc = e.keyCode != null ? e.keyCode : e.charCode; | |
if (kc < 37 || kc > 40) return true; | |
var d = new Date(dp._selectedDate).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.setDate(new Date(d), false); | |
return false; | |
} | |
// ie6 extension | |
this._el.onmousewheel = function (e) | |
{ | |
if (e == null) e = doc.parentWindow.event; | |
var n = - e.wheelDelta / 120; | |
var d = new Date(dp._selectedDate); | |
var m = d.getMonth() + n; | |
d.setMonth(m); | |
dp._dontChangeNone = true; | |
dp.setDate(d, false); | |
dp._dontChangeNone = false; | |
return false; | |
} | |
return this._el; | |
}; | |
DatePicker.prototype.setDate = function (oDate, isSelection) | |
{ | |
this._hideLabelPopup(); | |
// if null then set None | |
if (oDate == null) | |
{ | |
if (!this._none) | |
{ | |
this._none = true; | |
this._setTopLabel(); | |
this._updateTable(); | |
if (typeof this.onchange == "function") | |
this.onchange(); | |
} | |
if (isSelection) | |
this.onselect(); | |
return; | |
} | |
// if string or number create a Date object | |
if (typeof oDate == "string" || typeof oDate == "number") | |
{ | |
oDate = new Date(oDate); | |
} | |
// do not update if not really changed | |
if (this._selectedDate.getDate() != oDate.getDate() || | |
this._selectedDate.getMonth() != oDate.getMonth() || | |
this._selectedDate.getFullYear() != oDate.getFullYear() || | |
this._none) | |
{ | |
if (!this._dontChangeNone) | |
this._none = false; | |
this._selectedDate = new Date(oDate); | |
this._setTopLabel(); | |
this._updateTable(); | |
if (typeof this.onchange == "function") | |
this.onchange(); | |
if (isSelection) | |
this.onselect(); | |
} | |
if (!this._dontChangeNone) | |
this._none = false; | |
} | |
DatePicker.prototype.getDate = function () | |
{ | |
if (this._none) return null; | |
return new Date(this._selectedDate); // create a new instance | |
} | |
// creates the table elements and inserts them into the date picker | |
DatePicker.prototype._createTable = function (doc) | |
{ | |
var str, i; | |
var rows = 6; | |
var cols = 7; | |
var currentWeek = 0; | |
var table = doc.createElement("table"); | |
table.className = "gridTable"; | |
table.cellSpacing = 0; | |
var tBody = doc.createElement("tbody"); | |
table.appendChild(tBody); | |
// days row | |
var tr = doc.createElement("tr"); | |
tr.className = "daysRow"; | |
var td, tn; | |
var nbsp = String.fromCharCode(160); | |
for (i = 0; i < cols; i++) | |
{ | |
td = doc.createElement("td"); | |
td.appendChild(doc.createTextNode(nbsp)); | |
tr.appendChild(td); | |
} | |
tBody.appendChild(tr); | |
// upper line | |
tr = doc.createElement("tr"); | |
td = doc.createElement("td"); | |
td.className = "upperLine"; | |
td.colSpan = 7; | |
tr.appendChild(td); | |
tBody.appendChild(tr); | |
// rest | |
for (i = 0; i < rows; i++) | |
{ | |
tr = doc.createElement("tr"); | |
for (var j = 0; j < cols; j++) | |
{ | |
td = doc.createElement("td"); | |
td.appendChild(doc.createTextNode(nbsp)); | |
tr.appendChild(td); | |
} | |
tBody.appendChild(tr); | |
} | |
str += "</table>"; | |
if (this._table != null) | |
this._table.appendChild(table) | |
}; | |
// this method updates all the text nodes inside the table as well | |
// as all the classNames on the tds | |
DatePicker.prototype._updateTable = function () | |
{ | |
// if no element no need to continue | |
if (this._table == null) return; | |
var i; | |
var str = ""; | |
var rows = 6; | |
var cols = 7; | |
var currentWeek = 0; | |
var cells = new Array(rows); | |
this._matrix = new Array(rows) | |
for (i = 0; i < rows; i++) | |
{ | |
cells[i] = new Array(cols); | |
this._matrix[i] = new Array(cols); | |
} | |
// Set the tmpDate to this month | |
var tmpDate = new Date(this._selectedDate.getFullYear(), | |
this._selectedDate.getMonth(), 1); | |
var today = new Date(); | |
// go thorugh all days this month and store the text | |
// and the class name in the cells matrix | |
for (i = 1; i < 32; i++) | |
{ | |
tmpDate.setDate(i); | |
// convert to ISO, Monday is 0 and 6 is Sunday | |
var weekDay = ( tmpDate.getDay() + 6 ) % 7; | |
var colIndex = ( weekDay - this._firstWeekDay + 7 ) % 7; | |
if (tmpDate.getMonth() == this._selectedDate.getMonth()) | |
{ | |
var isToday = tmpDate.getDate() == today.getDate() && | |
tmpDate.getMonth() == today.getMonth() && | |
tmpDate.getFullYear() == today.getFullYear(); | |
cells[currentWeek][colIndex] = { text: "", className: "" }; | |
if (this._selectedDate.getDate() == tmpDate.getDate() && !this._none) | |
cells[currentWeek][colIndex].className += "selected "; | |
if (isToday) | |
cells[currentWeek][colIndex].className += "today "; | |
if (( tmpDate.getDay() + 6 ) % 7 == this._redWeekDay) // ISO | |
cells[currentWeek][colIndex].className += "red"; | |
cells[currentWeek][colIndex].text = | |
this._matrix[currentWeek][colIndex] = tmpDate.getDate(); | |
if (colIndex == 6) | |
currentWeek++; | |
} | |
} | |
// fix day letter order if not standard | |
var weekDays = DatePicker.days; | |
if (this._firstWeekDay != 0) | |
{ | |
weekDays = new Array(7); | |
for (i = 0; i < 7; i++) | |
weekDays[i] = DatePicker.days[ (i + this._firstWeekDay) % 7]; | |
} | |
// update text in days row | |
var tds = this._table.firstChild.tBodies[0].rows[0].cells; | |
for (i = 0; i < cols; i++) | |
tds[i].firstChild.data = weekDays[i]; | |
// update the text nodes and class names | |
var trs = this._table.firstChild.tBodies[0].rows; | |
var tmpCell; | |
var nbsp = String.fromCharCode(160); | |
for (var y = 0; y < rows; y++) | |
{ | |
for (var x = 0; x < cols; x++) | |
{ | |
tmpCell = trs[y + 2].cells[x]; | |
if (typeof cells[y][x] != "undefined") | |
{ | |
tmpCell.className = cells[y][x].className; | |
tmpCell.firstChild.data = cells[y][x].text; | |
} | |
else | |
{ | |
tmpCell.className = ""; | |
tmpCell.firstChild.data = nbsp; | |
} | |
} | |
} | |
} | |
// sets the label showing the year and selected month | |
DatePicker.prototype._setTopLabel = function () | |
{ | |
var str = this._selectedDate.getFullYear() + " " + DatePicker.months[ this._selectedDate.getMonth() ]; | |
if (this._topLabel != null) | |
this._topLabel.lastChild.data = str; | |
} | |
DatePicker.prototype.goToNextMonth = function () | |
{ | |
var d = new Date(this._selectedDate); | |
d.setDate(Math.min(d.getDate(), DatePicker.getDaysPerMonth(d.getMonth() + 1, | |
d.getFullYear()))); // no need to catch dec -> jan for the year | |
d.setMonth(d.getMonth() + 1); | |
this.setDate(d); | |
} | |
DatePicker.prototype.goToPreviousMonth = function () | |
{ | |
var d = new Date(this._selectedDate); | |
d.setDate(Math.min(d.getDate(), DatePicker.getDaysPerMonth(d.getMonth() - 1, | |
d.getFullYear()))); // no need to catch jan -> dec for the year | |
d.setMonth(d.getMonth() - 1); | |
this.setDate(d); | |
} | |
DatePicker.prototype.goToToday = function () | |
{ | |
if (this._none) | |
// change the selectedDate to force update if none was true | |
this._selectedDate = new Date(this._selectedDate + 10000000000); | |
this._none = false; | |
this.setDate(new Date(), true); | |
} | |
DatePicker.prototype.setShowToday = function (bShowToday) | |
{ | |
if (typeof bShowToday == "string") | |
bShowToday = !/false|0|no/i.test(bShowToday); | |
if (this._todayButton != null) | |
this._todayButton.style.visibility = bShowToday ? "visible" : "hidden"; | |
this._showToday = bShowToday; | |
} | |
DatePicker.prototype.getShowToday = function () | |
{ | |
return this._showToday; | |
} | |
DatePicker.prototype.setShowNone = function (bShowNone) | |
{ | |
if (typeof bShowNone == "string") | |
bShowNone = !/false|0|no/i.test(bShowNone); | |
if (this._noneButton != null) | |
this._noneButton.style.visibility = bShowNone ? "visible" : "hidden"; | |
this._showNone = bShowNone; | |
} | |
DatePicker.prototype.getShowNone = function () | |
{ | |
return this._showNone; | |
} | |
// 0 is monday and 6 is sunday as in the ISO standard | |
DatePicker.prototype.setFirstWeekDay = function (nFirstWeekDay) | |
{ | |
if (this._firstWeekDay != nFirstWeekDay) | |
{ | |
this._firstWeekDay = nFirstWeekDay; | |
this._updateTable(); | |
} | |
} | |
DatePicker.prototype.getFirstWeekDay = function () | |
{ | |
return this._firstWeekDay; | |
} | |
// 0 is monday and 6 is sunday as in the ISO standard | |
DatePicker.prototype.setRedWeekDay = function (nRedWeekDay) | |
{ | |
if (this._redWeekDay != nRedWeekDay) | |
{ | |
this._redWeekDay = nRedWeekDay; | |
this._updateTable(); | |
} | |
} | |
DatePicker.prototype.getRedWeekDay = function () | |
{ | |
return this._redWeekDay; | |
} | |
DatePicker.prototype._showLabelPopup = function () | |
{ | |
/* | |
this._labelPopup document.createElement( "DIV" ); | |
div.className = "month-popup"; | |
div.noWrap = true; | |
el.unselectable = div.unselectable = "on"; | |
el.onselectstart = div.onselectstart = function () { return false; }; | |
*/ | |
var dateContext = function (dp, d) | |
{ | |
return function (e) | |
{ | |
dp._dontChangeNone = true; | |
dp._hideLabelPopup(); | |
dp.setDate(d); | |
dp._dontChangeNone = false; | |
return false; | |
}; | |
}; | |
var dp = this; | |
// clear all old elements in the popup | |
while (this._labelPopup.hasChildNodes()) | |
this._labelPopup.removeChild(this._labelPopup.firstChild); | |
var a, tmp, tmp2; | |
for (var i = -3; i < 4; i++) | |
{ | |
tmp = new Date(this._selectedDate); | |
tmp2 = new Date(this._selectedDate); // need another tmp to catch year change when checking leap | |
tmp2.setDate(1); | |
tmp2.setMonth(tmp2.getMonth() + i); | |
tmp.setDate(Math.min(tmp.getDate(), DatePicker.getDaysPerMonth(tmp.getMonth() + i, | |
tmp2.getFullYear()))); | |
tmp.setMonth(tmp.getMonth() + i); | |
a = this._document.createElement("a"); | |
a.href = "javascript:void 0;"; | |
a.onclick = dateContext(dp, tmp); | |
a.appendChild(this._document.createTextNode(tmp.getFullYear() + " " + | |
DatePicker.months[ tmp.getMonth() ])); | |
if (i == 0) | |
a.className = "selected"; | |
this._labelPopup.appendChild(a); | |
} | |
this._topLabel.parentNode.insertBefore(this._labelPopup, this._topLabel.parentNode.firstChild); | |
}; | |
DatePicker.prototype._hideLabelPopup = function () | |
{ | |
if (this._labelPopup.parentNode) | |
this._labelPopup.parentNode.removeChild(this._labelPopup); | |
}; | |
DatePicker._daysPerMonth = [31,28,31,30,31,30,31,31,30,31,30,31]; | |
DatePicker.getDaysPerMonth = function (nMonth, nYear) | |
{ | |
nMonth = (nMonth + 12) % 12; | |
var res = DatePicker._daysPerMonth[nMonth]; | |
if (nMonth == 1) | |
{ | |
res += nYear % 4 == 0 && !(nYear % 400 == 0) ? 1 : 0; | |
} | |
return res; | |
}; |