1453 lines
52 KiB
JavaScript
1453 lines
52 KiB
JavaScript
|
/**
|
||
|
* Date selector
|
||
|
* @module Ink.UI.DatePicker_1
|
||
|
* @version 1
|
||
|
*/
|
||
|
|
||
|
Ink.createModule('Ink.UI.DatePicker', '1', ['Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1','Ink.Util.Array_1','Ink.Util.Date_1', 'Ink.Dom.Browser_1'], function(Common, Event, Css, InkElement, Selector, InkArray, InkDate ) {
|
||
|
'use strict';
|
||
|
|
||
|
// Clamp a number into a min/max limit
|
||
|
function clamp(n, min, max) {
|
||
|
if (n > max) { n = max; }
|
||
|
if (n < min) { n = min; }
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
function dateishFromYMDString(YMD) {
|
||
|
var split = YMD.split('-');
|
||
|
return dateishFromYMD(+split[0], +split[1] - 1, +split[2]);
|
||
|
}
|
||
|
|
||
|
function dateishFromYMD(year, month, day) {
|
||
|
return {_year: year, _month: month, _day: day};
|
||
|
}
|
||
|
|
||
|
function dateishFromDate(date) {
|
||
|
return {_year: date.getFullYear(), _month: date.getMonth(), _day: date.getDate()};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @class Ink.UI.DatePicker
|
||
|
* @constructor
|
||
|
* @version 1
|
||
|
*
|
||
|
* @param {String|DOMElement} selector
|
||
|
* @param {Object} [options] Options
|
||
|
* @param {Boolean} [options.autoOpen] Flag to automatically open the datepicker.
|
||
|
* @param {String} [options.cleanText] Text for the clean button. Defaults to 'Clear'.
|
||
|
* @param {String} [options.closeText] Text for the close button. Defaults to 'Close'.
|
||
|
* @param {String} [options.cssClass] CSS class to be applied on the datepicker
|
||
|
* @param {String|DOMElement} [options.pickerField] (if not using in an input[type="text"]) Element which displays the DatePicker when clicked. Defaults to an "open" link.
|
||
|
* @param {String} [options.dateRange] Enforce limits to year, month and day for the Date, ex: '1990-08-25:2020-11'
|
||
|
* @param {Boolean} [options.displayInSelect] Flag to display the component in a select element.
|
||
|
* @param {String|DOMElement} [options.dayField] (if using options.displayInSelect) `select` field with days.
|
||
|
* @param {String|DOMElement} [options.monthField] (if using options.displayInSelect) `select` field with months.
|
||
|
* @param {String|DOMElement} [options.yearField] (if using options.displayInSelect) `select` field with years.
|
||
|
* @param {String} [options.format] Date format string
|
||
|
* @param {Object} [options.month] Hash of month names. Defaults to portuguese month names. January is 1.
|
||
|
* @param {String} [options.nextLinkText] Text for the previous button. Defaults to '«'.
|
||
|
* @param {String} [options.ofText] Text to show between month and year. Defaults to ' of '.
|
||
|
* @param {Boolean} [options.onFocus] If the datepicker should open when the target element is focused. Defaults to true.
|
||
|
* @param {Function} [options.onMonthSelected] Callback to execute when the month is selected.
|
||
|
* @param {Function} [options.onSetDate] Callback to execute when the date is set.
|
||
|
* @param {Function} [options.onYearSelected] Callback to execute when the year is selected.
|
||
|
* @param {String} [options.position] Position for the datepicker. Either 'right' or 'bottom'. Defaults to 'right'.
|
||
|
* @param {String} [options.prevLinkText] Text for the previous button. Defaults to '«'.
|
||
|
* @param {Boolean} [options.showClean] If the clean button should be visible. Defaults to true.
|
||
|
* @param {Boolean} [options.showClose] If the close button should be visible. Defaults to true.
|
||
|
* @param {Boolean} [options.shy] If the datepicker should start automatically. Defaults to true.
|
||
|
* @param {String} [options.startDate] Date to define initial month. Must be in yyyy-mm-dd format.
|
||
|
* @param {Number} [options.startWeekDay] First day of the week. Sunday is zero. Defaults to 1 (Monday).
|
||
|
* @param {Function} [options.validYearFn] Callback to execute when 'rendering' the month (in the month view)
|
||
|
* @param {Function} [options.validMonthFn] Callback to execute when 'rendering' the month (in the month view)
|
||
|
* @param {Function} [options.validDayFn] Callback to execute when 'rendering' the day (in the month view)
|
||
|
* @param {Function} [options.nextValidDateFn] Function to calculate the next valid date, given the current. Useful when there's invalid dates or time frames.
|
||
|
* @param {Function} [options.prevValidDateFn] Function to calculate the previous valid date, given the current. Useful when there's invalid dates or time frames.
|
||
|
* @param {Object} [options.wDay] Hash of week day names. Sunday is 0. Defaults to { 0:'Sunday', 1:'Monday', etc...
|
||
|
* @param {String} [options.yearRange] Enforce limits to year for the Date, ex: '1990:2020' (deprecated)
|
||
|
*
|
||
|
* @sample Ink_UI_DatePicker_1.html
|
||
|
*/
|
||
|
var DatePicker = function() {
|
||
|
Common.BaseUIComponent.apply(this, arguments);
|
||
|
};
|
||
|
|
||
|
DatePicker._name = 'DatePicker_1';
|
||
|
|
||
|
DatePicker._optionDefinition = {
|
||
|
autoOpen: ['Boolean', false],
|
||
|
cleanText: ['String', 'Clear'],
|
||
|
closeText: ['String', 'Close'],
|
||
|
pickerField: ['Element', null],
|
||
|
containerElement:['Element', null],
|
||
|
cssClass: ['String', 'ink-calendar bottom'],
|
||
|
dateRange: ['String', null],
|
||
|
|
||
|
// use this in a <select>
|
||
|
displayInSelect: ['Boolean', false],
|
||
|
dayField: ['Element', null],
|
||
|
monthField: ['Element', null],
|
||
|
yearField: ['Element', null],
|
||
|
|
||
|
format: ['String', 'yyyy-mm-dd'],
|
||
|
nextLinkText: ['String', '»'],
|
||
|
ofText: ['String', ' of '],
|
||
|
onFocus: ['Boolean', true],
|
||
|
onMonthSelected: ['Function', null],
|
||
|
onSetDate: ['Function', null],
|
||
|
onYearSelected: ['Function', null],
|
||
|
position: ['String', 'right'],
|
||
|
prevLinkText: ['String', '«'],
|
||
|
showClean: ['Boolean', true],
|
||
|
showClose: ['Boolean', true],
|
||
|
shy: ['Boolean', true],
|
||
|
startDate: ['String', null], // format yyyy-mm-dd,
|
||
|
startWeekDay: ['Number', 1],
|
||
|
|
||
|
// Validation
|
||
|
validDayFn: ['Function', null],
|
||
|
validMonthFn: ['Function', null],
|
||
|
validYearFn: ['Function', null],
|
||
|
nextValidDateFn: ['Function', null],
|
||
|
prevValidDateFn: ['Function', null],
|
||
|
yearRange: ['String', null],
|
||
|
|
||
|
// Text
|
||
|
month: ['Object', {
|
||
|
1:'January',
|
||
|
2:'February',
|
||
|
3:'March',
|
||
|
4:'April',
|
||
|
5:'May',
|
||
|
6:'June',
|
||
|
7:'July',
|
||
|
8:'August',
|
||
|
9:'September',
|
||
|
10:'October',
|
||
|
11:'November',
|
||
|
12:'December'
|
||
|
}],
|
||
|
wDay: ['Object', {
|
||
|
0:'Sunday',
|
||
|
1:'Monday',
|
||
|
2:'Tuesday',
|
||
|
3:'Wednesday',
|
||
|
4:'Thursday',
|
||
|
5:'Friday',
|
||
|
6:'Saturday'
|
||
|
}]
|
||
|
};
|
||
|
|
||
|
DatePicker.prototype = {
|
||
|
/**
|
||
|
* Initialization function. Called by the constructor and receives the same parameters.
|
||
|
*
|
||
|
* @method _init
|
||
|
* @private
|
||
|
*/
|
||
|
_init: function(){
|
||
|
this._options.format = this._dateParsers[ this._options.format ] || this._options.format;
|
||
|
|
||
|
this._hoverPicker = false;
|
||
|
|
||
|
this._picker = this._options.pickerField || null;
|
||
|
|
||
|
this._setMinMax( this._options.dateRange || this._options.yearRange );
|
||
|
|
||
|
if(this._options.startDate) {
|
||
|
this.setDate( this._options.startDate );
|
||
|
} else if (this._element && this._element.value) {
|
||
|
this.setDate( this._element.value );
|
||
|
} else {
|
||
|
this.setDate(new Date());
|
||
|
}
|
||
|
|
||
|
if (this._options.startWeekDay < 0 || this._options.startWeekDay > 6) {
|
||
|
Ink.warn('Ink.UI.DatePicker_1: option "startWeekDay" must be between 0 (sunday) and 6 (saturday)');
|
||
|
this._options.startWeekDay = clamp(this._options.startWeekDay, 0, 6);
|
||
|
}
|
||
|
|
||
|
Ink.extendObj(this._options,this._lang || {});
|
||
|
|
||
|
this._render();
|
||
|
this._listenToContainerObjectEvents();
|
||
|
},
|
||
|
|
||
|
_validate: function () {
|
||
|
if(this._options.displayInSelect &&
|
||
|
!(this._options.dayField && this._options.monthField && this._options.yearField)){
|
||
|
throw new Error(
|
||
|
'Ink.UI.DatePicker: displayInSelect option enabled.'+
|
||
|
'Please specify dayField, monthField and yearField selectors.');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Renders the DatePicker's markup.
|
||
|
*
|
||
|
* @method _render
|
||
|
* @private
|
||
|
*/
|
||
|
_render: function() {
|
||
|
this._containerObject = document.createElement('div');
|
||
|
|
||
|
this._containerObject.className = this._options.cssClass + ' ink-datepicker-calendar hide-all';
|
||
|
|
||
|
this._renderSuperTopBar();
|
||
|
|
||
|
var calendarTop = document.createElement("div");
|
||
|
calendarTop.className = 'ink-calendar-top';
|
||
|
|
||
|
this._monthDescContainer = document.createElement("div");
|
||
|
this._monthDescContainer.className = 'ink-calendar-month_desc';
|
||
|
|
||
|
this._monthPrev = document.createElement('div');
|
||
|
this._monthPrev.className = 'ink-calendar-prev';
|
||
|
this._monthPrev.appendChild(InkElement.create('a', {
|
||
|
href: '#prev',
|
||
|
className: 'change_month_prev',
|
||
|
setHTML: this._options.prevLinkText
|
||
|
}));
|
||
|
|
||
|
this._monthNext = document.createElement('div');
|
||
|
this._monthNext.className = 'ink-calendar-next';
|
||
|
this._monthNext.appendChild(InkElement.create('a', {
|
||
|
href: '#next',
|
||
|
className: 'change_month_next',
|
||
|
setHTML: this._options.nextLinkText
|
||
|
}));
|
||
|
|
||
|
calendarTop.appendChild(this._monthPrev);
|
||
|
calendarTop.appendChild(this._monthDescContainer);
|
||
|
calendarTop.appendChild(this._monthNext);
|
||
|
|
||
|
this._monthContainer = document.createElement("div");
|
||
|
this._monthContainer.className = 'ink-calendar-month';
|
||
|
|
||
|
this._containerObject.appendChild(calendarTop);
|
||
|
this._containerObject.appendChild(this._monthContainer);
|
||
|
|
||
|
this._monthSelector = this._renderMonthSelector();
|
||
|
this._containerObject.appendChild(this._monthSelector);
|
||
|
|
||
|
this._yearSelector = document.createElement('ul');
|
||
|
this._yearSelector.className = 'ink-calendar-year-selector';
|
||
|
|
||
|
this._containerObject.appendChild(this._yearSelector);
|
||
|
|
||
|
if(!this._options.onFocus || this._options.displayInSelect){
|
||
|
if(!this._options.pickerField){
|
||
|
this._picker = InkElement.create('a', {
|
||
|
href: '#open_cal',
|
||
|
setHTML: 'open',
|
||
|
insertBottom: this._element.parentNode,
|
||
|
className: 'ink-datepicker-picker-field'
|
||
|
});
|
||
|
} else {
|
||
|
this._picker = Common.elOrSelector(this._options.pickerField, 'pickerField');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._appendDatePickerToDom();
|
||
|
|
||
|
this._renderMonth();
|
||
|
|
||
|
this._monthChanger = InkElement.create('a', {
|
||
|
href: '#monthchanger',
|
||
|
className: 'ink-calendar-link-month',
|
||
|
setTextContent: this._options.month[this._month + 1]
|
||
|
});
|
||
|
|
||
|
this._ofText = InkElement.create('span', {
|
||
|
className: 'ink-calendar-of-text',
|
||
|
setHTML: this._options.ofText
|
||
|
});
|
||
|
|
||
|
this._yearChanger = InkElement.create('a', {
|
||
|
href: '#yearchanger',
|
||
|
className: 'ink-calendar-link-year',
|
||
|
setTextContent: this._year
|
||
|
});
|
||
|
|
||
|
this._monthDescContainer.appendChild(this._monthChanger);
|
||
|
this._monthDescContainer.appendChild(this._ofText);
|
||
|
this._monthDescContainer.appendChild(this._yearChanger);
|
||
|
|
||
|
if (!this._options.inline) {
|
||
|
this._addOpenCloseEvents();
|
||
|
} else {
|
||
|
this.show();
|
||
|
}
|
||
|
this._addDateChangeHandlersToInputs();
|
||
|
},
|
||
|
|
||
|
_addDateChangeHandlersToInputs: function () {
|
||
|
var fields = this._element;
|
||
|
if (this._options.displayInSelect) {
|
||
|
fields = [
|
||
|
this._options.dayField,
|
||
|
this._options.monthField,
|
||
|
this._options.yearField];
|
||
|
}
|
||
|
Event.observeMulti(fields ,'change', Ink.bindEvent(function(){
|
||
|
this._updateDate( );
|
||
|
this._showDefaultView( );
|
||
|
this.setDate( );
|
||
|
if ( !this._inline && !this._hoverPicker ) {
|
||
|
this._hide(true);
|
||
|
}
|
||
|
},this));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Shows the calendar.
|
||
|
*
|
||
|
* @method show
|
||
|
**/
|
||
|
show: function () {
|
||
|
this._updateDate();
|
||
|
this._renderMonth();
|
||
|
Css.removeClassName(this._containerObject, 'hide-all');
|
||
|
},
|
||
|
|
||
|
_addOpenCloseEvents: function () {
|
||
|
var opener = this._picker || this._element;
|
||
|
|
||
|
Event.observe(opener, 'click', Ink.bindEvent(function(e){
|
||
|
Event.stop(e);
|
||
|
this.show();
|
||
|
},this));
|
||
|
|
||
|
if (this._options.autoOpen) {
|
||
|
this.show();
|
||
|
}
|
||
|
|
||
|
if(!this._options.displayInSelect){
|
||
|
Event.observe(opener, 'blur', Ink.bindEvent(function() {
|
||
|
if ( !this._hoverPicker ) {
|
||
|
this._hide(true);
|
||
|
}
|
||
|
},this));
|
||
|
}
|
||
|
|
||
|
if (this._options.shy) {
|
||
|
// Close the picker when clicking elsewhere.
|
||
|
Event.observe(document,'click',Ink.bindEvent(function(e){
|
||
|
var target = Event.element(e);
|
||
|
|
||
|
// "elsewhere" is outside any of these elements:
|
||
|
var cannotBe = [
|
||
|
this._options.dayField,
|
||
|
this._options.monthField,
|
||
|
this._options.yearField,
|
||
|
this._picker,
|
||
|
this._element
|
||
|
];
|
||
|
|
||
|
for (var i = 0, len = cannotBe.length; i < len; i++) {
|
||
|
if (cannotBe[i] && InkElement.descendantOf(cannotBe[i], target)) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._hide(true);
|
||
|
},this));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Creates the markup of the view with months.
|
||
|
*
|
||
|
* @method _renderMonthSelector
|
||
|
* @private
|
||
|
*/
|
||
|
_renderMonthSelector: function () {
|
||
|
var selector = document.createElement('ul');
|
||
|
selector.className = 'ink-calendar-month-selector';
|
||
|
|
||
|
var ulSelector = document.createElement('ul');
|
||
|
for(var mon=1; mon<=12; mon++){
|
||
|
ulSelector.appendChild(this._renderMonthButton(mon));
|
||
|
|
||
|
if (mon % 4 === 0) {
|
||
|
selector.appendChild(ulSelector);
|
||
|
ulSelector = document.createElement('ul');
|
||
|
}
|
||
|
}
|
||
|
return selector;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Renders a single month button.
|
||
|
*/
|
||
|
_renderMonthButton: function (mon) {
|
||
|
var liMonth = document.createElement('li');
|
||
|
liMonth.appendChild(InkElement.create('a', {
|
||
|
'data-cal-month': mon,
|
||
|
setTextContent: this._options.month[mon].substring(0, 3)
|
||
|
}));
|
||
|
return liMonth;
|
||
|
},
|
||
|
|
||
|
_appendDatePickerToDom: function () {
|
||
|
if(this._options.containerElement) {
|
||
|
var appendTarget =
|
||
|
Common.elOrSelector(this._options.containerElement);
|
||
|
appendTarget.appendChild(this._containerObject);
|
||
|
}
|
||
|
|
||
|
var parentIsControl = Selector.matchesSelector(
|
||
|
this._element.parentNode,
|
||
|
'.ink-form .control-group .control');
|
||
|
|
||
|
if (parentIsControl) {
|
||
|
this._wrapper = this._element.parentNode;
|
||
|
this._wrapperIsControl = true;
|
||
|
} else {
|
||
|
this._wrapper = InkElement.create('div', { className: 'ink-datepicker-wrapper' });
|
||
|
InkElement.wrap(this._element, this._wrapper);
|
||
|
}
|
||
|
|
||
|
InkElement.insertAfter(this._containerObject, this._element);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Render the topmost bar with the "close" and "clear" buttons.
|
||
|
*/
|
||
|
_renderSuperTopBar: function () {
|
||
|
if((!this._options.showClose) || (!this._options.showClean)){ return; }
|
||
|
|
||
|
this._superTopBar = document.createElement("div");
|
||
|
this._superTopBar.className = 'ink-calendar-top-options';
|
||
|
if(this._options.showClean){
|
||
|
this._superTopBar.appendChild(InkElement.create('a', {
|
||
|
className: 'clean',
|
||
|
setHTML: this._options.cleanText
|
||
|
}));
|
||
|
}
|
||
|
if(this._options.showClose){
|
||
|
this._superTopBar.appendChild(InkElement.create('a', {
|
||
|
className: 'close',
|
||
|
setHTML: this._options.closeText
|
||
|
}));
|
||
|
}
|
||
|
this._containerObject.appendChild(this._superTopBar);
|
||
|
},
|
||
|
|
||
|
_listenToContainerObjectEvents: function () {
|
||
|
Event.observe(this._containerObject,'mouseover',Ink.bindEvent(function(e){
|
||
|
Event.stop( e );
|
||
|
this._hoverPicker = true;
|
||
|
},this));
|
||
|
|
||
|
Event.observe(this._containerObject,'mouseout',Ink.bindEvent(function(e){
|
||
|
Event.stop( e );
|
||
|
this._hoverPicker = false;
|
||
|
},this));
|
||
|
|
||
|
Event.observe(this._containerObject,'click',Ink.bindEvent(this._onClick, this));
|
||
|
},
|
||
|
|
||
|
_onClick: function(e){
|
||
|
var elem = Event.element(e);
|
||
|
|
||
|
if (Css.hasClassName(elem, 'ink-calendar-off')) {
|
||
|
Event.stopDefault(e);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
Event.stop(e);
|
||
|
|
||
|
// Relative changers
|
||
|
this._onRelativeChangerClick(elem);
|
||
|
|
||
|
// Absolute changers
|
||
|
this._onAbsoluteChangerClick(elem);
|
||
|
|
||
|
// Mode changers
|
||
|
if (Css.hasClassName(elem, 'ink-calendar-link-month')) {
|
||
|
this._showMonthSelector();
|
||
|
} else if (Css.hasClassName(elem, 'ink-calendar-link-year')) {
|
||
|
this._showYearSelector();
|
||
|
} else if(Css.hasClassName(elem, 'clean')){
|
||
|
this._clean();
|
||
|
} else if(Css.hasClassName(elem, 'close')){
|
||
|
this._hide(false);
|
||
|
}
|
||
|
|
||
|
this._updateDescription();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Handles click events on a changer (« ») for next/prev year/month
|
||
|
* @method _onChangerClick
|
||
|
* @private
|
||
|
**/
|
||
|
_onRelativeChangerClick: function (elem) {
|
||
|
var changeYear = {
|
||
|
change_year_next: 1,
|
||
|
change_year_prev: -1
|
||
|
};
|
||
|
var changeMonth = {
|
||
|
change_month_next: 1,
|
||
|
change_month_prev: -1
|
||
|
};
|
||
|
|
||
|
if( elem.className in changeMonth ) {
|
||
|
this._updateCal(changeMonth[elem.className]);
|
||
|
} else if( elem.className in changeYear ) {
|
||
|
this._showYearSelector(changeYear[elem.className]);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Handles click events on an atom-changer (day button, month button, year button)
|
||
|
*
|
||
|
* @method _onAbsoluteChangerClick
|
||
|
* @private
|
||
|
*/
|
||
|
_onAbsoluteChangerClick: function (elem) {
|
||
|
var elemData = InkElement.data(elem);
|
||
|
|
||
|
if( Number(elemData.calDay) ){
|
||
|
this.setDate(new Date(this._year, this._month, elemData.calDay));
|
||
|
this._hide();
|
||
|
} else if( Number(elemData.calMonth) ) {
|
||
|
this._month = Number(elemData.calMonth) - 1;
|
||
|
this._showDefaultView();
|
||
|
this._updateCal();
|
||
|
} else if( Number(elemData.calYear) ){
|
||
|
this._changeYear(Number(elemData.calYear));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_changeYear: function (year) {
|
||
|
year = +year;
|
||
|
if(!isNaN(year)){
|
||
|
this._year = year;
|
||
|
if( typeof this._options.onYearSelected === 'function' ){
|
||
|
this._options.onYearSelected(this, {
|
||
|
'year': this._year
|
||
|
});
|
||
|
}
|
||
|
this._showMonthSelector();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_clean: function () {
|
||
|
if(this._options.displayInSelect){
|
||
|
this._options.yearField.selectedIndex = 0;
|
||
|
this._options.monthField.selectedIndex = 0;
|
||
|
this._options.dayField.selectedIndex = 0;
|
||
|
} else {
|
||
|
this._element.value = '';
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Hides the DatePicker.
|
||
|
* If the component is shy (options.shy), behaves differently.
|
||
|
*
|
||
|
* @method _hide
|
||
|
* @param {Boolean} [blur] If false, forces hiding even if the component is shy.
|
||
|
*/
|
||
|
_hide: function(blur) {
|
||
|
blur = blur === undefined ? true : blur;
|
||
|
if (blur === false || (blur && this._options.shy)) {
|
||
|
Css.addClassName(this._containerObject, 'hide-all');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the range of dates allowed to be selected in the Date Picker
|
||
|
*
|
||
|
* @method _setMinMax
|
||
|
* @param {String} dateRange Two dates separated by a ':'. Example: 2013-01-01:2013-12-12
|
||
|
* @private
|
||
|
*/
|
||
|
_setMinMax: function( dateRange ) {
|
||
|
var self = this;
|
||
|
|
||
|
var noMinLimit = {
|
||
|
_year: -Number.MAX_VALUE,
|
||
|
_month: 0,
|
||
|
_day: 1
|
||
|
};
|
||
|
|
||
|
var noMaxLimit = {
|
||
|
_year: Number.MAX_VALUE,
|
||
|
_month: 11,
|
||
|
_day: 31
|
||
|
};
|
||
|
|
||
|
function noLimits() {
|
||
|
self._min = noMinLimit;
|
||
|
self._max = noMaxLimit;
|
||
|
}
|
||
|
|
||
|
if (!dateRange) { return noLimits(); }
|
||
|
|
||
|
var dates = dateRange.split( ':' );
|
||
|
var rDate = /^(\d{4})((\-)(\d{1,2})((\-)(\d{1,2}))?)?$/;
|
||
|
|
||
|
InkArray.each([
|
||
|
{name: '_min', date: dates[0], noLim: noMinLimit},
|
||
|
{name: '_max', date: dates[1], noLim: noMaxLimit}
|
||
|
], Ink.bind(function (data) {
|
||
|
|
||
|
var lim = data.noLim;
|
||
|
|
||
|
if ( data.date.toUpperCase() === 'NOW' ) {
|
||
|
var now = new Date();
|
||
|
lim = dateishFromDate(now);
|
||
|
} else if (data.date.toUpperCase() === 'EVER') {
|
||
|
lim = data.noLim;
|
||
|
} else if ( rDate.test( data.date ) ) {
|
||
|
lim = dateishFromYMDString(data.date);
|
||
|
|
||
|
lim._month = clamp(lim._month, 0, 11);
|
||
|
lim._day = clamp(lim._day, 1, this._daysInMonth( lim._year, lim._month + 1 ));
|
||
|
}
|
||
|
|
||
|
this[data.name] = lim;
|
||
|
}, this));
|
||
|
|
||
|
// Should be equal, or min should be smaller
|
||
|
var valid = this._dateCmp(this._max, this._min) !== -1;
|
||
|
|
||
|
if (!valid) {
|
||
|
noLimits();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks if a date is between the valid range.
|
||
|
* Starts by checking if the date passed is valid. If not, will fallback to the 'today' date.
|
||
|
* Then checks if the all params are inside of the date range specified. If not, it will fallback to the nearest valid date (either Min or Max).
|
||
|
*
|
||
|
* @method _fitDateToRange
|
||
|
* @param {Number} year Year with 4 digits (yyyy)
|
||
|
* @param {Number} month Month
|
||
|
* @param {Number} day Day
|
||
|
* @return {Array} Array with the final processed date.
|
||
|
* @private
|
||
|
*/
|
||
|
_fitDateToRange: function( date ) {
|
||
|
if ( !this._isValidDate( date ) ) {
|
||
|
date = dateishFromDate(new Date());
|
||
|
}
|
||
|
|
||
|
if (this._dateCmp(date, this._min) === -1) {
|
||
|
return Ink.extendObj({}, this._min);
|
||
|
} else if (this._dateCmp(date, this._max) === 1) {
|
||
|
return Ink.extendObj({}, this._max);
|
||
|
}
|
||
|
|
||
|
return Ink.extendObj({}, date); // date is okay already, just copy it.
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks whether a date is within the valid date range
|
||
|
* @method _dateWithinRange
|
||
|
* @param year
|
||
|
* @param month
|
||
|
* @param day
|
||
|
* @return {Boolean}
|
||
|
* @private
|
||
|
*/
|
||
|
_dateWithinRange: function (date) {
|
||
|
if (!arguments.length) {
|
||
|
date = this;
|
||
|
}
|
||
|
|
||
|
return (!this._dateAboveMax(date) &&
|
||
|
(!this._dateBelowMin(date)));
|
||
|
},
|
||
|
|
||
|
_dateAboveMax: function (date) {
|
||
|
return this._dateCmp(date, this._max) === 1;
|
||
|
},
|
||
|
|
||
|
_dateBelowMin: function (date) {
|
||
|
return this._dateCmp(date, this._min) === -1;
|
||
|
},
|
||
|
|
||
|
_dateCmp: function (self, oth) {
|
||
|
return this._dateCmpUntil(self, oth, '_day');
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* _dateCmp with varied precision. You can compare down to the day field, or, just to the month.
|
||
|
* // the following two dates are considered equal because we asked
|
||
|
* // _dateCmpUntil to just check up to the years.
|
||
|
*
|
||
|
* _dateCmpUntil({_year: 2000, _month: 10}, {_year: 2000, _month: 11}, '_year') === 0
|
||
|
*/
|
||
|
_dateCmpUntil: function (self, oth, depth) {
|
||
|
var props = ['_year', '_month', '_day'];
|
||
|
var i = -1;
|
||
|
|
||
|
do {
|
||
|
i++;
|
||
|
if (self[props[i]] > oth[props[i]]) { return 1; }
|
||
|
else if (self[props[i]] < oth[props[i]]) { return -1; }
|
||
|
} while (props[i] !== depth &&
|
||
|
self[props[i + 1]] !== undefined && oth[props[i + 1]] !== undefined);
|
||
|
|
||
|
return 0;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the markup in the default view mode (showing the days).
|
||
|
* Also disables the previous and next buttons in case they don't meet the range requirements.
|
||
|
*
|
||
|
* @method _showDefaultView
|
||
|
* @private
|
||
|
*/
|
||
|
_showDefaultView: function(){
|
||
|
this._yearSelector.style.display = 'none';
|
||
|
this._monthSelector.style.display = 'none';
|
||
|
this._monthPrev.childNodes[0].className = 'change_month_prev';
|
||
|
this._monthNext.childNodes[0].className = 'change_month_next';
|
||
|
|
||
|
if ( !this._getPrevMonth() ) {
|
||
|
this._monthPrev.childNodes[0].className = 'action_inactive';
|
||
|
}
|
||
|
|
||
|
if ( !this._getNextMonth() ) {
|
||
|
this._monthNext.childNodes[0].className = 'action_inactive';
|
||
|
}
|
||
|
|
||
|
this._monthContainer.style.display = 'block';
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates the date shown on the datepicker
|
||
|
*
|
||
|
* @method _updateDate
|
||
|
* @private
|
||
|
*/
|
||
|
_updateDate: function(){
|
||
|
var dataParsed;
|
||
|
if(!this._options.displayInSelect && this._element.value){
|
||
|
dataParsed = this._parseDate(this._element.value);
|
||
|
} else if (this._options.displayInSelect) {
|
||
|
dataParsed = {
|
||
|
_year: this._options.yearField[this._options.yearField.selectedIndex].value,
|
||
|
_month: this._options.monthField[this._options.monthField.selectedIndex].value - 1,
|
||
|
_day: this._options.dayField[this._options.dayField.selectedIndex].value
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (dataParsed) {
|
||
|
dataParsed = this._fitDateToRange(dataParsed);
|
||
|
this._year = dataParsed._year;
|
||
|
this._month = dataParsed._month;
|
||
|
this._day = dataParsed._day;
|
||
|
}
|
||
|
this._setDate();
|
||
|
this._updateDescription();
|
||
|
this._renderMonth();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates the date description shown at the top of the datepicker
|
||
|
*
|
||
|
* EG "12 de November"
|
||
|
*
|
||
|
* @method _updateDescription
|
||
|
* @private
|
||
|
*/
|
||
|
_updateDescription: function(){
|
||
|
InkElement.setTextContent(this._monthChanger, this._options.month[this._month + 1]);
|
||
|
InkElement.setTextContent(this._ofText, this._options.ofText);
|
||
|
InkElement.setTextContent(this._yearChanger, this._year);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Renders the year selector view of the datepicker
|
||
|
*
|
||
|
* @method _showYearSelector
|
||
|
* @private
|
||
|
*/
|
||
|
_showYearSelector: function(inc){
|
||
|
this._incrementViewingYear(inc);
|
||
|
|
||
|
var firstYear = this._year - (this._year % 10);
|
||
|
var thisYear = firstYear - 1;
|
||
|
|
||
|
InkElement.setHTML(this._yearSelector, '');
|
||
|
var yearUl = InkElement.create('ul');
|
||
|
this._yearSelector.appendChild(yearUl);
|
||
|
|
||
|
if (thisYear > this._min._year) {
|
||
|
var prevYearLi = InkElement.create('li');
|
||
|
|
||
|
prevYearLi.appendChild(InkElement.create('a', {
|
||
|
href: '#year_prev',
|
||
|
className: 'change_year_prev',
|
||
|
setHTML: this._options.prevLinkText
|
||
|
}));
|
||
|
|
||
|
yearUl.appendChild(prevYearLi);
|
||
|
} else {
|
||
|
yearUl.appendChild(InkElement.create('li', { setHTML: ' ' }));
|
||
|
}
|
||
|
|
||
|
for (var i=1; i < 11; i++){
|
||
|
if (i % 4 === 0){
|
||
|
yearUl = InkElement.create('ul');
|
||
|
this._yearSelector.appendChild(yearUl);
|
||
|
}
|
||
|
|
||
|
thisYear = firstYear + i - 1;
|
||
|
|
||
|
yearUl.appendChild(this._getYearButton(thisYear));
|
||
|
}
|
||
|
|
||
|
if (thisYear < this._max._year) {
|
||
|
var nextYearLi = InkElement.create('li');
|
||
|
|
||
|
nextYearLi.appendChild(InkElement.create('a', {
|
||
|
href: '#year_next',
|
||
|
className: 'change_year_next',
|
||
|
setHTML: this._options.nextLinkText
|
||
|
}));
|
||
|
|
||
|
yearUl.appendChild(nextYearLi);
|
||
|
} else {
|
||
|
yearUl.appendChild(InkElement.create('li', { setHTML: ' ' }));
|
||
|
}
|
||
|
|
||
|
this._monthPrev.childNodes[0].className = 'action_inactive';
|
||
|
this._monthNext.childNodes[0].className = 'action_inactive';
|
||
|
this._monthSelector.style.display = 'none';
|
||
|
this._monthContainer.style.display = 'none';
|
||
|
this._yearSelector.style.display = 'block';
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* For the year selector.
|
||
|
*
|
||
|
* Update this._year, to find the next decade or use nextValidDateFn to find it.
|
||
|
*/
|
||
|
_incrementViewingYear: function (inc) {
|
||
|
if (!inc) { return; }
|
||
|
|
||
|
var year = +this._year + inc*10;
|
||
|
year = year - year % 10;
|
||
|
if ( year > this._max._year || year + 9 < this._min._year){
|
||
|
return;
|
||
|
}
|
||
|
this._year = +this._year + inc*10;
|
||
|
},
|
||
|
|
||
|
_getYearButton: function (thisYear) {
|
||
|
var className = '';
|
||
|
|
||
|
if (!this._acceptableYear({ _year: thisYear })) {
|
||
|
className = 'ink-calendar-off';
|
||
|
} else if (thisYear === this._year) {
|
||
|
className = 'ink-calendar-on';
|
||
|
}
|
||
|
|
||
|
var li = InkElement.create('li');
|
||
|
|
||
|
li.appendChild(InkElement.create('a', {
|
||
|
href: '#',
|
||
|
'data-cal-year': thisYear,
|
||
|
className: className,
|
||
|
setTextContent: thisYear
|
||
|
}));
|
||
|
|
||
|
return li;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Show the month selector (happens when you click a year, or the "month" link.
|
||
|
* @method _showMonthSelector
|
||
|
* @private
|
||
|
*/
|
||
|
_showMonthSelector: function () {
|
||
|
this._yearSelector.style.display = 'none';
|
||
|
this._monthContainer.style.display = 'none';
|
||
|
this._monthPrev.childNodes[0].className = 'action_inactive';
|
||
|
this._monthNext.childNodes[0].className = 'action_inactive';
|
||
|
this._addMonthClassNames();
|
||
|
this._monthSelector.style.display = 'block';
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This function returns the given date in the dateish format
|
||
|
*
|
||
|
* @method _parseDate
|
||
|
* @param {String} dateStr A date on a string.
|
||
|
* @private
|
||
|
*/
|
||
|
_parseDate: function(dateStr){
|
||
|
var date = InkDate.set( this._options.format , dateStr );
|
||
|
if (date) {
|
||
|
return dateishFromDate(date);
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks if a date is valid
|
||
|
*
|
||
|
* @method _isValidDate
|
||
|
* @param {Dateish} date
|
||
|
* @private
|
||
|
* @return {Boolean} True if the date is valid, false otherwise
|
||
|
*/
|
||
|
_isValidDate: function(date){
|
||
|
var yearRegExp = /^\d{4}$/;
|
||
|
var validOneOrTwo = /^\d{1,2}$/;
|
||
|
return (
|
||
|
yearRegExp.test(date._year) &&
|
||
|
validOneOrTwo.test(date._month) &&
|
||
|
validOneOrTwo.test(date._day) &&
|
||
|
+date._month + 1 >= 1 &&
|
||
|
+date._month + 1 <= 12 &&
|
||
|
+date._day >= 1 &&
|
||
|
+date._day <= this._daysInMonth(date._year, date._month + 1)
|
||
|
);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks if a given date is an valid format.
|
||
|
*
|
||
|
* @method _isDate
|
||
|
* @param {String} format A date format.
|
||
|
* @param {String} dateStr A date on a string.
|
||
|
* @private
|
||
|
* @return {Boolean} True if the given date is valid according to the given format
|
||
|
*/
|
||
|
_isDate: function(format, dateStr){
|
||
|
try {
|
||
|
if (typeof format === 'undefined'){
|
||
|
return false;
|
||
|
}
|
||
|
var date = InkDate.set( format , dateStr );
|
||
|
if( date && this._isValidDate( dateishFromDate(date) )) {
|
||
|
return true;
|
||
|
}
|
||
|
} catch (ex) {}
|
||
|
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
_acceptableDay: function (date) {
|
||
|
return this._acceptableDateComponent(date, 'validDayFn');
|
||
|
},
|
||
|
|
||
|
_acceptableMonth: function (date) {
|
||
|
return this._acceptableDateComponent(date, 'validMonthFn');
|
||
|
},
|
||
|
|
||
|
_acceptableYear: function (date) {
|
||
|
return this._acceptableDateComponent(date, 'validYearFn');
|
||
|
},
|
||
|
|
||
|
/** DRY base for the above 2 functions */
|
||
|
_acceptableDateComponent: function (date, userCb) {
|
||
|
if (this._options[userCb]) {
|
||
|
return this._callUserCallbackBool(this._options[userCb], date);
|
||
|
} else {
|
||
|
return this._dateWithinRange(date);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method returns the date written with the format specified on the options
|
||
|
*
|
||
|
* @method _writeDateInFormat
|
||
|
* @private
|
||
|
* @return {String} Returns the current date of the object in the specified format
|
||
|
*/
|
||
|
_writeDateInFormat:function(){
|
||
|
return InkDate.get( this._options.format , this.getDate());
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method allows the user to set the DatePicker's date on run-time.
|
||
|
*
|
||
|
* @method setDate
|
||
|
* @param {Date|String} dateString A Date object, or date string in yyyy-mm-dd format.
|
||
|
* @public
|
||
|
*/
|
||
|
setDate: function( dateString ) {
|
||
|
if (dateString && typeof dateString.getDate === 'function') {
|
||
|
dateString = [ dateString.getFullYear(),
|
||
|
dateString.getMonth() + 1, dateString.getDate() ].join('-');
|
||
|
}
|
||
|
|
||
|
if ( /\d{4}-\d{1,2}-\d{1,2}/.test( dateString ) ) {
|
||
|
var auxDate = dateString.split( '-' );
|
||
|
this._year = +auxDate[ 0 ];
|
||
|
this._month = +auxDate[ 1 ] - 1;
|
||
|
this._day = +auxDate[ 2 ];
|
||
|
}
|
||
|
|
||
|
this._setDate( );
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets the currently selected date as a JavaScript date.
|
||
|
*
|
||
|
* @method getDate
|
||
|
*/
|
||
|
getDate: function () {
|
||
|
if (!this._day) {
|
||
|
throw 'Ink.UI.DatePicker: Still picking a date. Cannot getDate now!';
|
||
|
}
|
||
|
return new Date(this._year, this._month, this._day);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the chosen date on the target input field
|
||
|
*
|
||
|
* @method _setDate
|
||
|
* @param {DOMElement} objClicked Clicked object inside the DatePicker's calendar.
|
||
|
* @private
|
||
|
*/
|
||
|
_setDate : function( objClicked ) {
|
||
|
if (objClicked) {
|
||
|
var data = InkElement.data(objClicked);
|
||
|
this._day = (+data.calDay) || this._day;
|
||
|
}
|
||
|
|
||
|
var dt = this._fitDateToRange(this);
|
||
|
|
||
|
this._year = dt._year;
|
||
|
this._month = dt._month;
|
||
|
this._day = dt._day;
|
||
|
|
||
|
if(!this._options.displayInSelect){
|
||
|
this._element.value = this._writeDateInFormat();
|
||
|
} else {
|
||
|
this._options.dayField.value = this._day;
|
||
|
this._options.monthField.value = this._month + 1;
|
||
|
this._options.yearField.value = this._year;
|
||
|
}
|
||
|
|
||
|
if(this._options.onSetDate) {
|
||
|
this._options.onSetDate( this , { date : this.getDate() } );
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Makes the necessary work to update the calendar
|
||
|
* when choosing a different month
|
||
|
*
|
||
|
* @method _updateCal
|
||
|
* @param {Number} inc Indicates previous or next month
|
||
|
* @private
|
||
|
*/
|
||
|
_updateCal: function(inc){
|
||
|
if( typeof this._options.onMonthSelected === 'function' ){
|
||
|
this._options.onMonthSelected(this, {
|
||
|
'year': this._year,
|
||
|
'month' : this._month
|
||
|
});
|
||
|
}
|
||
|
if (inc && this._updateMonth(inc) === null) {
|
||
|
return;
|
||
|
}
|
||
|
this._renderMonth();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function that returns the number of days on a given month on a given year
|
||
|
*
|
||
|
* @method _daysInMonth
|
||
|
* @param {Number} _y - year
|
||
|
* @param {Number} _m - month
|
||
|
* @private
|
||
|
* @return {Number} The number of days on a given month on a given year
|
||
|
*/
|
||
|
_daysInMonth: function(_y,_m){
|
||
|
var exceptions = {
|
||
|
2: ((_y % 400 === 0) || (_y % 4 === 0 && _y % 100 !== 0)) ? 29 : 28,
|
||
|
4: 30,
|
||
|
6: 30,
|
||
|
9: 30,
|
||
|
11: 30
|
||
|
};
|
||
|
|
||
|
return exceptions[_m] || 31;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Updates the calendar when a different month is chosen
|
||
|
*
|
||
|
* @method _updateMonth
|
||
|
* @param {Number} incValue - indicates previous or next month
|
||
|
* @private
|
||
|
*/
|
||
|
_updateMonth: function(incValue){
|
||
|
var date;
|
||
|
if (incValue > 0) {
|
||
|
date = this._getNextMonth();
|
||
|
} else if (incValue < 0) {
|
||
|
date = this._getPrevMonth();
|
||
|
}
|
||
|
if (!date) { return null; }
|
||
|
this._year = date._year;
|
||
|
this._month = date._month;
|
||
|
this._day = date._day;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Get the next month we can show.
|
||
|
*/
|
||
|
_getNextMonth: function (date) {
|
||
|
return this._tryLeap( date, 'Month', 'next', function (d) {
|
||
|
d._month += 1;
|
||
|
if (d._month > 11) {
|
||
|
d._month = 0;
|
||
|
d._year += 1;
|
||
|
}
|
||
|
return d;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Get the previous month we can show.
|
||
|
*/
|
||
|
_getPrevMonth: function (date) {
|
||
|
return this._tryLeap( date, 'Month', 'prev', function (d) {
|
||
|
d._month -= 1;
|
||
|
if (d._month < 0) {
|
||
|
d._month = 11;
|
||
|
d._year -= 1;
|
||
|
}
|
||
|
return d;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Get the next year we can show.
|
||
|
*/
|
||
|
_getPrevYear: function (date) {
|
||
|
return this._tryLeap( date, 'Year', 'prev', function (d) {
|
||
|
d._year -= 1;
|
||
|
return d;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Get the next year we can show.
|
||
|
*/
|
||
|
_getNextYear: function (date) {
|
||
|
return this._tryLeap( date, 'Year', 'next', function (d) {
|
||
|
d._year += 1;
|
||
|
return d;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* DRY base for a function which tries to get the next or previous valid year or month.
|
||
|
*
|
||
|
* It checks if we can go forward by using _dateCmp with atomic
|
||
|
* precision (this means, {_year} for leaping years, and
|
||
|
* {_year, month} for leaping months), then it tries to get the
|
||
|
* result from the user-supplied callback (nextDateFn or prevDateFn),
|
||
|
* and when this is not present, advance the date forward using the
|
||
|
* `advancer` callback.
|
||
|
*/
|
||
|
_tryLeap: function (date, atomName, directionName, advancer) {
|
||
|
date = date || { _year: this._year, _month: this._month, _day: this._day };
|
||
|
|
||
|
var maxOrMin = directionName === 'prev' ? '_min' : '_max';
|
||
|
var boundary = this[maxOrMin];
|
||
|
|
||
|
// Check if we're by the boundary of min/max year/month
|
||
|
if (this._dateCmpUntil(date, boundary, atomName) === 0) {
|
||
|
return null; // We're already at the boundary. Bail.
|
||
|
}
|
||
|
|
||
|
var leapUserCb = this._options[directionName + 'ValidDateFn'];
|
||
|
if (leapUserCb) {
|
||
|
return this._callUserCallbackDate(leapUserCb, date);
|
||
|
} else {
|
||
|
date = advancer(date);
|
||
|
}
|
||
|
|
||
|
date = this._fitDateToRange(date);
|
||
|
|
||
|
return this['_acceptable' + atomName](date) ? date : null;
|
||
|
},
|
||
|
|
||
|
_getNextDecade: function (date) {
|
||
|
date = date || { _year: this._year, _month: this._month, _day: this._day };
|
||
|
var decade = this._getCurrentDecade(date);
|
||
|
if (decade + 10 > this._max._year) { return null; }
|
||
|
return decade + 10;
|
||
|
},
|
||
|
|
||
|
_getPrevDecade: function (date) {
|
||
|
date = date || { _year: this._year, _month: this._month, _day: this._day };
|
||
|
var decade = this._getCurrentDecade(date);
|
||
|
if (decade - 10 < this._min._year) { return null; }
|
||
|
return decade - 10;
|
||
|
},
|
||
|
|
||
|
/** Returns the decade given a date or year*/
|
||
|
_getCurrentDecade: function (year) {
|
||
|
year = year ? (year._year || year) : this._year;
|
||
|
return Math.floor(year / 10) * 10; // Round to first place
|
||
|
},
|
||
|
|
||
|
_callUserCallbackBase: function (cb, date) {
|
||
|
return cb.call(this, date._year, date._month + 1, date._day);
|
||
|
},
|
||
|
|
||
|
_callUserCallbackBool: function (cb, date) {
|
||
|
return !!this._callUserCallbackBase(cb, date);
|
||
|
},
|
||
|
|
||
|
_callUserCallbackDate: function (cb, date) {
|
||
|
var ret = this._callUserCallbackBase(cb, date);
|
||
|
return ret ? dateishFromDate(ret) : null;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Key-value object that (for a given key) points to the correct parsing format for the DatePicker
|
||
|
* @property _dateParsers
|
||
|
* @type {Object}
|
||
|
* @readOnly
|
||
|
*/
|
||
|
_dateParsers: {
|
||
|
'yyyy-mm-dd' : 'Y-m-d' ,
|
||
|
'yyyy/mm/dd' : 'Y/m/d' ,
|
||
|
'yy-mm-dd' : 'y-m-d' ,
|
||
|
'yy/mm/dd' : 'y/m/d' ,
|
||
|
'dd-mm-yyyy' : 'd-m-Y' ,
|
||
|
'dd/mm/yyyy' : 'd/m/Y' ,
|
||
|
'dd-mm-yy' : 'd-m-y' ,
|
||
|
'dd/mm/yy' : 'd/m/y' ,
|
||
|
'mm/dd/yyyy' : 'm/d/Y' ,
|
||
|
'mm-dd-yyyy' : 'm-d-Y'
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Renders the current month
|
||
|
*
|
||
|
* @method _renderMonth
|
||
|
* @private
|
||
|
*/
|
||
|
_renderMonth: function(){
|
||
|
var month = this._month;
|
||
|
var year = this._year;
|
||
|
|
||
|
this._showDefaultView();
|
||
|
|
||
|
InkElement.setHTML(this._monthContainer, '');
|
||
|
|
||
|
this._monthContainer.appendChild(
|
||
|
this._getMonthCalendarHeader(this._options.startWeekDay));
|
||
|
|
||
|
this._monthContainer.appendChild(
|
||
|
this._getDayButtons(year, month));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Figure out where the first day of a month lies
|
||
|
* in the first row of the calendar.
|
||
|
*
|
||
|
* having options.startWeekDay === 0
|
||
|
*
|
||
|
* Su Mo Tu We Th Fr Sa
|
||
|
* 1 <- The "1" is in the 7th day. return 6.
|
||
|
* 2 3 4 5 6 7 8
|
||
|
* 9 10 11 12 13 14 15
|
||
|
* 16 17 18 19 20 21 22
|
||
|
* 23 24 25 26 27 28 29
|
||
|
* 30 31
|
||
|
*
|
||
|
* This obviously changes according to the user option "startWeekDay"
|
||
|
**/
|
||
|
_getFirstDayIndex: function (year, month) {
|
||
|
var wDayFirst = (new Date( year , month , 1 )).getDay(); // Sunday=0
|
||
|
var startWeekDay = this._options.startWeekDay || 0; // Sunday=0
|
||
|
|
||
|
var result = wDayFirst - startWeekDay;
|
||
|
|
||
|
result %= 7;
|
||
|
|
||
|
if (result < 0) {
|
||
|
result += 6;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
_getDayButtons: function (year, month) {
|
||
|
var daysInMonth = this._daysInMonth(year, month + 1);
|
||
|
|
||
|
var ret = document.createDocumentFragment();
|
||
|
|
||
|
var ul = InkElement.create('ul');
|
||
|
ret.appendChild(ul);
|
||
|
|
||
|
var firstDayIndex = this._getFirstDayIndex(year, month);
|
||
|
|
||
|
// Add padding if the first day of the month is not monday.
|
||
|
for (var i = 0; i < firstDayIndex; i ++) {
|
||
|
ul.appendChild(InkElement.create('li', {
|
||
|
className: 'ink-calendar-empty',
|
||
|
setHTML: ' '
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
for (var day = 1; day <= daysInMonth; day++) {
|
||
|
if ((day - 1 + firstDayIndex) % 7 === 0){ // new week, new UL
|
||
|
ul = InkElement.create('ul');
|
||
|
ret.appendChild(ul);
|
||
|
}
|
||
|
|
||
|
ul.appendChild(this._getDayButton(year, month, day));
|
||
|
}
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Get the HTML markup for a single day in month view, given year, month, day.
|
||
|
*
|
||
|
* @method _getDayButtonHtml
|
||
|
* @private
|
||
|
*/
|
||
|
_getDayButton: function (year, month, day) {
|
||
|
var attrs = {};
|
||
|
var date = dateishFromYMD(year, month, day);
|
||
|
|
||
|
if (!this._acceptableDay(date)) {
|
||
|
attrs.className = 'ink-calendar-off';
|
||
|
} else {
|
||
|
attrs['data-cal-day'] = day;
|
||
|
|
||
|
if (this._day && this._dateCmp(date, this) === 0) {
|
||
|
attrs.className = 'ink-calendar-on';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
attrs.setTextContent = day;
|
||
|
|
||
|
var dayButton = InkElement.create('li');
|
||
|
dayButton.appendChild(InkElement.create('a', attrs));
|
||
|
return dayButton;
|
||
|
},
|
||
|
|
||
|
/** Write the top bar of the calendar (M T W T F S S) */
|
||
|
_getMonthCalendarHeader: function (startWeekDay) {
|
||
|
var header = InkElement.create('ul', {
|
||
|
className: 'ink-calendar-header'
|
||
|
});
|
||
|
|
||
|
var wDay;
|
||
|
for(var i=0; i<7; i++){
|
||
|
wDay = (startWeekDay + i) % 7;
|
||
|
header.appendChild(InkElement.create('li', {
|
||
|
setTextContent: this._options.wDay[wDay].substring(0, 1)
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
return header;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method adds class names to month buttons, to visually distinguish.
|
||
|
*
|
||
|
* @method _addMonthClassNames
|
||
|
* @param {DOMElement} parent DOMElement where all the months are.
|
||
|
* @private
|
||
|
*/
|
||
|
_addMonthClassNames: function(parent){
|
||
|
InkArray.forEach(
|
||
|
(parent || this._monthSelector).getElementsByTagName('a'),
|
||
|
Ink.bindMethod(this, '_addMonthButtonClassNames'));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add the ink-calendar-on className if the given button is the current month,
|
||
|
* otherwise add the ink-calendar-off className if the given button refers to
|
||
|
* an unacceptable month (given dateRange and validMonthFn)
|
||
|
*/
|
||
|
_addMonthButtonClassNames: function (btn) {
|
||
|
var data = InkElement.data(btn);
|
||
|
if (!data.calMonth) { throw 'not a calendar month button!'; }
|
||
|
|
||
|
var month = +data.calMonth - 1;
|
||
|
|
||
|
if ( month === this._month ) {
|
||
|
Css.addClassName( btn, 'ink-calendar-on' ); // This month
|
||
|
Css.removeClassName( btn, 'ink-calendar-off' );
|
||
|
} else {
|
||
|
Css.removeClassName( btn, 'ink-calendar-on' ); // Not this month
|
||
|
|
||
|
var toDisable = !this._acceptableMonth({_year: this._year, _month: month});
|
||
|
Css.addRemoveClassName( btn, 'ink-calendar-off', toDisable);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/*
|
||
|
* // TODO implement this
|
||
|
* Prototype's method to allow the 'i18n files' to change all objects' language at once.
|
||
|
* @param {Object} options Object with the texts' configuration.
|
||
|
* @param {String} options.closeText Text of the close anchor
|
||
|
* @param {String} options.cleanText Text of the clean text anchor
|
||
|
* @param {String} options.prevLinkText "Previous" link's text
|
||
|
* @param {String} options.nextLinkText "Next" link's text
|
||
|
* @param {String} options.ofText The text "of", present in 'May of 2013'
|
||
|
* @param {Object} options.month An object with keys from 1 to 12 for the full months' names
|
||
|
* @param {Object} options.wDay An object with keys from 0 to 6 for the full weekdays' names
|
||
|
* @public
|
||
|
*/
|
||
|
lang: function( options ){
|
||
|
this._lang = options;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This calls the rendering of the selected month. (Deprecated: use show() instead)
|
||
|
*
|
||
|
*/
|
||
|
showMonth: function(){
|
||
|
this._renderMonth();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks if the calendar screen is in 'select day' mode
|
||
|
*
|
||
|
* @method isMonthRendered
|
||
|
* @return {Boolean} True if the calendar screen is in 'select day' mode
|
||
|
* @public
|
||
|
*/
|
||
|
isMonthRendered: function(){
|
||
|
var header = Selector.select('.ink-calendar-header', this._containerObject)[0];
|
||
|
|
||
|
return ((Css.getStyle(header.parentNode,'display') !== 'none') &&
|
||
|
(Css.getStyle(header.parentNode.parentNode,'display') !== 'none') );
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Destroys this datepicker, removing it from the page.
|
||
|
*
|
||
|
* @method destroy
|
||
|
* @public
|
||
|
**/
|
||
|
destroy: function () {
|
||
|
InkElement.unwrap(this._element);
|
||
|
InkElement.remove(this._wrapper);
|
||
|
InkElement.remove(this._containerObject);
|
||
|
Common.unregisterInstance.call(this);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Common.createUIComponent(DatePicker);
|
||
|
|
||
|
return DatePicker;
|
||
|
});
|