blob: 6e7b645c4823e7a66e7a7df2105ea2ece1e41e16 [file] [log] [blame]
/*----------------------------------------------------------------------------\
| 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;
};