blob: 13955dc79322e010fbc13efa6c620739b41db573 [file] [log] [blame]
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.widget.DatePicker");
dojo.require("dojo.date.common");
dojo.require("dojo.date.format");
dojo.require("dojo.date.serialize");
dojo.require("dojo.widget.*");
dojo.require("dojo.widget.HtmlWidget");
dojo.require("dojo.event.*");
dojo.require("dojo.dom");
dojo.require("dojo.html.style");
dojo.widget.defineWidget(
"dojo.widget.DatePicker",
dojo.widget.HtmlWidget,
{
/*
summary:
Base class for a stand-alone DatePicker widget
which makes it easy to select a date, or switch by month and/or year.
description:
A stand-alone DatePicker widget that makes it
easy to select a date, or increment by week, month, and/or year.
It is designed to be used on its own, or inside of other widgets to
(see dojo.widget.DropdownDatePicker) or other similar combination widgets.
Dates attributes passed in the `RFC 3339` format:
http://www.faqs.org/rfcs/rfc3339.html (2005-06-30T08:05:00-07:00)
so that they are serializable and locale-independent.
usage:
var datePicker = dojo.widget.createWidget("DatePicker", {},
dojo.byId("datePickerNode"));
<div dojoType="DatePicker"></div>
*/
//start attributes
// value: String|Date
// form value property if =='today' will be today's date
value: "",
// name: String
// name of the form element
name: "",
// displayWeeks: Integer
// total weeks to display default
displayWeeks: 6,
// adjustWeeks: Boolean
// if true, weekly size of calendar changes to acomodate the month if false, 42 day format is used
adjustWeeks: false,
// startDate: String|Date
// first available date in the calendar set
startDate: "1492-10-12",
// endDate: String|Date
// last available date in the calendar set
endDate: "2941-10-12",
// weekStartsOn: Integer
// adjusts the first day of the week 0==Sunday..6==Saturday
weekStartsOn: "",
// storedDate: String
// deprecated use value instead
storedDate: "",
// staticDisplay: Boolean
// disable all incremental controls, must pick a date in the current display
staticDisplay: false,
// dayWidth: String
// how to render the names of the days in the header. see dojo.date.getDayNames
dayWidth: 'narrow',
classNames: {
// summary:
// stores a list of class names that may be overriden
// TODO: this is not good; can't be adjusted via markup, etc. since it's an array
previous: "previousMonth",
disabledPrevious: "previousMonthDisabled",
current: "currentMonth",
disabledCurrent: "currentMonthDisabled",
next: "nextMonth",
disabledNext: "nextMonthDisabled",
currentDate: "currentDate",
selectedDate: "selectedItem"
},
templatePath: dojo.uri.dojoUri("src/widget/templates/DatePicker.html"),
templateCssPath: dojo.uri.dojoUri("src/widget/templates/DatePicker.css"),
postMixInProperties: function(){
// summary: see dojo.widget.DomWidget
dojo.widget.DatePicker.superclass.postMixInProperties.apply(this, arguments);
if(this.storedDate){
dojo.deprecated("dojo.widget.DatePicker", "use 'value' instead of 'storedDate'", "0.5");
this.value=this.storedDate;
}
this.startDate = dojo.date.fromRfc3339(this.startDate);
this.endDate = dojo.date.fromRfc3339(this.endDate);
this.startDate.setHours(0,0,0,0); //adjust startDate to be exactly midnight
this.endDate.setHours(24,0,0,-1); //adjusting endDate to be a fraction of a second before midnight
if(!this.weekStartsOn){
this.weekStartsOn=dojo.date.getFirstDayOfWeek(this.lang);
}
this.today = new Date();
this.today.setHours(0,0,0,0);
if(typeof(this.value)=='string'&&this.value.toLowerCase()=='today'){
this.value = new Date();
}else if(this.value && (typeof this.value=="string") && (this.value.split("-").length > 2)) {
this.value = dojo.date.fromRfc3339(this.value);
this.value.setHours(0,0,0,0);
}
},
fillInTemplate: function(args, frag) {
// summary: see dojo.widget.DomWidget
dojo.widget.DatePicker.superclass.fillInTemplate.apply(this, arguments);
// Copy style info from input node to output node
var source = this.getFragNodeRef(frag);
dojo.html.copyStyle(this.domNode, source);
this.weekTemplate = dojo.dom.removeNode(this.calendarWeekTemplate);
this._preInitUI(this.value ? this.value : this.today, false, true); //init UI with date selected ONLY if user supplies one
// Insert localized day names in the template
var dayLabels = dojo.lang.unnest(dojo.date.getNames('days', this.dayWidth, 'standAlone', this.lang)); //if we dont use unnest, we risk modifying the dayLabels array inside of dojo.date and screwing up other calendars on the page
if(this.weekStartsOn > 0){
//adjust dayLabels for different first day of week. ie: Monday or Thursday instead of Sunday
for(var i=0;i<this.weekStartsOn;i++){
dayLabels.push(dayLabels.shift());
}
}
var dayLabelNodes = this.dayLabelsRow.getElementsByTagName("td");
for(i=0; i<7; i++) {
dayLabelNodes.item(i).innerHTML = dayLabels[i];
}
if(this.value){
this.setValue(this.value);
}
},
getValue: function() {
// summary: return current date in RFC 3339 format
return dojo.date.toRfc3339(new Date(this.value),'dateOnly'); /*String*/
},
getDate: function() {
// summary: return current date as a Date object
return this.value; /*Date*/
},
setValue: function(/*Date|String*/rfcDate) {
//summary: set the current date from RFC 3339 formatted string or a date object, synonymous with setDate
this.setDate(rfcDate);
},
setDate: function(/*Date|String*/dateObj) {
//summary: set the current date and update the UI
if(typeof dateObj=="string"){
this.value = dojo.date.fromRfc3339(dateObj);
}else{
this.value = new Date(dateObj);
}
this.value.setHours(0,0,0,0);
if(this.selectedNode!=null){
dojo.html.removeClass(this.selectedNode,this.classNames.selectedDate);
}
if(this.clickedNode!=null){
dojo.html.addClass(this.clickedNode,this.classNames.selectedDate);
this.selectedNode = this.clickedNode;
}else{
//only call this if setDate was called by means other than clicking a date
this._preInitUI(this.value,false,true);
}
this.clickedNode=null;
this.onValueChanged(this.value);
},
_preInitUI: function(dateObj,initFirst,initUI) {
/*
To get a sense of what month to highlight, we initialize on
the first Saturday of each month, since that will be either the first
of two or the second of three months being partially displayed, and
then work forwards and backwards from that point.
*/
//initFirst is to tell _initFirstDay if you want first day of the displayed calendar, or first day of the week for dateObj
//initUI tells preInitUI to go ahead and run initUI if set to true
if(dateObj<this.startDate||dateObj>this.endDate){
dateObj = new Date((dateObj<this.startDate)?this.startDate:this.endDate);
}
this.firstDay = this._initFirstDay(dateObj,initFirst);
this.selectedIsUsed = false;
this.currentIsUsed = false;
var nextDate = new Date(this.firstDay);
var tmpMonth = nextDate.getMonth();
this.curMonth = new Date(nextDate);
this.curMonth.setDate(nextDate.getDate()+6); //first saturday gives us the current Month
this.curMonth.setDate(1);
if(this.displayWeeks=="" || this.adjustWeeks){
this.adjustWeeks = true;
this.displayWeeks = Math.ceil((dojo.date.getDaysInMonth(this.curMonth) + this._getAdjustedDay(this.curMonth))/7);
}
var days = this.displayWeeks*7; //init total days to display
if(dojo.date.diff(this.startDate,this.endDate, dojo.date.dateParts.DAY) < days){
this.staticDisplay = true;
if(dojo.date.diff(nextDate,this.endDate, dojo.date.dateParts.DAY) > days){
this._preInitUI(this.startDate,true,false);
nextDate = new Date(this.firstDay);
}
this.curMonth = new Date(nextDate);
this.curMonth.setDate(nextDate.getDate()+6);
this.curMonth.setDate(1);
var curClass = (nextDate.getMonth() == this.curMonth.getMonth())?'current':'previous';
}
if(initUI){
this._initUI(days);
}
},
_initUI: function(days) {
dojo.dom.removeChildren(this.calendarDatesContainerNode);
for(var i=0;i<this.displayWeeks;i++){
this.calendarDatesContainerNode.appendChild(this.weekTemplate.cloneNode(true));
}
var nextDate = new Date(this.firstDay);
this._setMonthLabel(this.curMonth.getMonth());
this._setYearLabels(this.curMonth.getFullYear());
var calendarNodes = this.calendarDatesContainerNode.getElementsByTagName("td");
var calendarRows = this.calendarDatesContainerNode.getElementsByTagName("tr");
var currentCalendarNode;
for(i=0;i<days;i++){
//this is our new UI loop... one loop to rule them all, and in the datepicker bind them
currentCalendarNode = calendarNodes.item(i);
currentCalendarNode.innerHTML = nextDate.getDate();
var curClass = (nextDate.getMonth()<this.curMonth.getMonth())?'previous':(nextDate.getMonth()==this.curMonth.getMonth())?'current':'next';
var mappedClass = curClass;
if(this._isDisabledDate(nextDate)){
var classMap={previous:"disabledPrevious",current:"disabledCurrent",next:"disabledNext"};
mappedClass=classMap[curClass];
}
dojo.html.setClass(currentCalendarNode, this._getDateClassName(nextDate, mappedClass));
if(dojo.html.hasClass(currentCalendarNode,this.classNames.selectedDate)){
this.selectedNode = currentCalendarNode;
}
nextDate = dojo.date.add(nextDate, dojo.date.dateParts.DAY, 1);
}
this.lastDay = dojo.date.add(nextDate,dojo.date.dateParts.DAY,-1);
this._initControls();
},
_initControls: function(){
var d = this.firstDay;
var d2 = this.lastDay;
var decWeek, incWeek, decMonth, incMonth, decYear, incYear;
decWeek = incWeek = decMonth = incMonth = decYear = incYear = !this.staticDisplay;
with(dojo.date.dateParts){
var add = dojo.date.add;
if(decWeek && add(d,DAY,(-1*(this._getAdjustedDay(d)+1)))<this.startDate){
decWeek = decMonth = decYear = false;
}
if(incWeek && d2>this.endDate){
incWeek = incMonth = incYear = false;
}
if(decMonth && add(d,DAY,-1)<this.startDate){
decMonth = decYear = false;
}
if(incMonth && add(d2,DAY,1)>this.endDate){
incMonth = incYear = false;
}
if(decYear && add(d2,YEAR,-1)<this.startDate){
decYear = false;
}
if(incYear && add(d,YEAR,1)>this.endDate){
incYear = false;
}
}
function enableControl(node, enabled){
dojo.html.setVisibility(node, enabled ? '' : 'hidden');
}
enableControl(this.decreaseWeekNode,decWeek);
enableControl(this.increaseWeekNode,incWeek);
enableControl(this.decreaseMonthNode,decMonth);
enableControl(this.increaseMonthNode,incMonth);
enableControl(this.previousYearLabelNode,decYear);
enableControl(this.nextYearLabelNode,incYear);
},
_incrementWeek: function(evt) {
var d = new Date(this.firstDay);
switch(evt.target) {
case this.increaseWeekNode.getElementsByTagName("img").item(0):
case this.increaseWeekNode:
var tmpDate = dojo.date.add(d, dojo.date.dateParts.WEEK, 1);
if(tmpDate < this.endDate){
d = dojo.date.add(d, dojo.date.dateParts.WEEK, 1);
}
break;
case this.decreaseWeekNode.getElementsByTagName("img").item(0):
case this.decreaseWeekNode:
if(d >= this.startDate){
d = dojo.date.add(d, dojo.date.dateParts.WEEK, -1);
}
break;
}
this._preInitUI(d,true,true);
},
_incrementMonth: function(evt) {
var d = new Date(this.curMonth);
var tmpDate = new Date(this.firstDay);
switch(evt.currentTarget) {
case this.increaseMonthNode.getElementsByTagName("img").item(0):
case this.increaseMonthNode:
tmpDate = dojo.date.add(tmpDate, dojo.date.dateParts.DAY, this.displayWeeks*7);
if(tmpDate < this.endDate){
d = dojo.date.add(d, dojo.date.dateParts.MONTH, 1);
}else{
var revertToEndDate = true;
}
break;
case this.decreaseMonthNode.getElementsByTagName("img").item(0):
case this.decreaseMonthNode:
if(tmpDate > this.startDate){
d = dojo.date.add(d, dojo.date.dateParts.MONTH, -1);
}else{
var revertToStartDate = true;
}
break;
}
if(revertToStartDate){
d = new Date(this.startDate);
}else if(revertToEndDate){
d = new Date(this.endDate);
}
this._preInitUI(d,false,true);
},
_incrementYear: function(evt) {
var year = this.curMonth.getFullYear();
var tmpDate = new Date(this.firstDay);
switch(evt.target) {
case this.nextYearLabelNode:
tmpDate = dojo.date.add(tmpDate, dojo.date.dateParts.YEAR, 1);
if(tmpDate<this.endDate){
year++;
}else{
var revertToEndDate = true;
}
break;
case this.previousYearLabelNode:
tmpDate = dojo.date.add(tmpDate, dojo.date.dateParts.YEAR, -1);
if(tmpDate>this.startDate){
year--;
}else{
var revertToStartDate = true;
}
break;
}
var d;
if(revertToStartDate){
d = new Date(this.startDate);
}else if(revertToEndDate){
d = new Date(this.endDate);
}else{
d = new Date(year, this.curMonth.getMonth(), 1);
}
this._preInitUI(d,false,true);
},
onIncrementWeek: function(/*Event*/evt) {
// summary: handler for increment week event
evt.stopPropagation();
if(!this.staticDisplay){
this._incrementWeek(evt);
}
},
onIncrementMonth: function(/*Event*/evt) {
// summary: handler for increment month event
evt.stopPropagation();
if(!this.staticDisplay){
this._incrementMonth(evt);
}
},
onIncrementYear: function(/*Event*/evt) {
// summary: handler for increment year event
evt.stopPropagation();
if(!this.staticDisplay){
this._incrementYear(evt);
}
},
_setMonthLabel: function(monthIndex) {
this.monthLabelNode.innerHTML = dojo.date.getNames('months', 'wide', 'standAlone', this.lang)[monthIndex];
},
_setYearLabels: function(year) {
var y = year - 1;
var that = this;
function f(n){
that[n+"YearLabelNode"].innerHTML =
dojo.date.format(new Date(y++, 0), {formatLength:'yearOnly', locale:that.lang});
}
f("previous");
f("current");
f("next");
},
_getDateClassName: function(date, monthState) {
var currentClassName = this.classNames[monthState];
//we use Number comparisons because 2 dateObjects never seem to equal each other otherwise
if ((!this.selectedIsUsed && this.value) && (Number(date) == Number(this.value))) {
currentClassName = this.classNames.selectedDate + " " + currentClassName;
this.selectedIsUsed = true;
}
if((!this.currentIsUsed) && (Number(date) == Number(this.today))) {
currentClassName = currentClassName + " " + this.classNames.currentDate;
this.currentIsUsed = true;
}
return currentClassName;
},
onClick: function(/*Event*/evt) {
//summary: the click event handler
dojo.event.browser.stopEvent(evt);
},
_handleUiClick: function(/*Event*/evt) {
var eventTarget = evt.target;
if(eventTarget.nodeType != dojo.dom.ELEMENT_NODE){eventTarget = eventTarget.parentNode;}
dojo.event.browser.stopEvent(evt);
this.selectedIsUsed = this.todayIsUsed = false;
var month = this.curMonth.getMonth();
var year = this.curMonth.getFullYear();
if(dojo.html.hasClass(eventTarget, this.classNames["disabledPrevious"])||dojo.html.hasClass(eventTarget, this.classNames["disabledCurrent"])||dojo.html.hasClass(eventTarget, this.classNames["disabledNext"])){
return; //this date is disabled... ignore it
}else if (dojo.html.hasClass(eventTarget, this.classNames["next"])) {
month = ++month % 12;
if(month===0){++year;}
} else if (dojo.html.hasClass(eventTarget, this.classNames["previous"])) {
month = --month % 12;
if(month==11){--year;}
}
this.clickedNode = eventTarget;
this.setDate(new Date(year, month, eventTarget.innerHTML));
},
onValueChanged: function(/*Date*/date) {
//summary: the set date event handler
},
_isDisabledDate: function(dateObj){
if(dateObj<this.startDate||dateObj>this.endDate){
return true;
}
return this.isDisabledDate(dateObj, this.lang);
},
isDisabledDate: function(/*Date*/dateObj, /*String?*/locale){
// summary:
// May be overridden to disable certain dates in the calendar e.g. isDisabledDate=dojo.date.isWeekend
return false; // Boolean
},
_initFirstDay: function(/*Date*/dateObj, /*Boolean*/adj){
//adj: false for first day of month, true for first day of week adjusted by startOfWeek
var d = new Date(dateObj);
if(!adj){d.setDate(1);}
d.setDate(d.getDate()-this._getAdjustedDay(d,this.weekStartsOn));
d.setHours(0,0,0,0);
return d; // Date
},
_getAdjustedDay: function(/*Date*/dateObj){
//summary: used to adjust date.getDay() values to the new values based on the current first day of the week value
var days = [0,1,2,3,4,5,6];
if(this.weekStartsOn>0){
for(var i=0;i<this.weekStartsOn;i++){
days.unshift(days.pop());
}
}
return days[dateObj.getDay()]; // Number: 0..6 where 0=Sunday
},
destroy: function(){
dojo.widget.DatePicker.superclass.destroy.apply(this, arguments);
dojo.html.destroyNode(this.weekTemplate);
}
}
);