gemini-datepicker
Version:
A full-featured datepicker jquery plugin.
1,116 lines (1,039 loc) • 99.6 kB
JavaScript
/**
* Created by Greg Zhang
*/
;(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(jQuery);
}
})(function ($) {
'use strict';
var dateUtils = (function () {
var fecha = {};
var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
var twoDigits = /\d\d?/;
var threeDigits = /\d{3}/;
var fourDigits = /\d{4}/;
var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
var noop = function () {
};
function shorten(arr, sLen) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; i++) {
newArr.push(arr[i].substr(0, sLen));
}
return newArr;
}
function monthUpdate(arrName) {
return function (d, v, i18n) {
var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase());
if (~index) {
d.month = index;
}
};
}
function pad(val, len) {
val = String(val);
len = len || 2;
while (val.length < len) {
val = '0' + val;
}
return val;
}
var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var monthNamesShort = shorten(monthNames, 3);
var dayNamesShort = shorten(dayNames, 3);
fecha.i18n = {
dayNamesShort: dayNamesShort,
dayNames: dayNames,
monthNamesShort: monthNamesShort,
monthNames: monthNames,
amPm: ['am', 'pm'],
DoFn: function DoFn(D) {
return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
}
};
var formatFlags = {
D: function(dateObj) {
return dateObj.getDay();
},
DD: function(dateObj) {
return pad(dateObj.getDay());
},
Do: function(dateObj, i18n) {
return i18n.DoFn(dateObj.getDate());
},
d: function(dateObj) {
return dateObj.getDate();
},
dd: function(dateObj) {
return pad(dateObj.getDate());
},
ddd: function(dateObj, i18n) {
return i18n.dayNamesShort[dateObj.getDay()];
},
dddd: function(dateObj, i18n) {
return i18n.dayNames[dateObj.getDay()];
},
M: function(dateObj) {
return dateObj.getMonth() + 1;
},
MM: function(dateObj) {
return pad(dateObj.getMonth() + 1);
},
MMM: function(dateObj, i18n) {
return i18n.monthNamesShort[dateObj.getMonth()];
},
MMMM: function(dateObj, i18n) {
return i18n.monthNames[dateObj.getMonth()];
},
yy: function(dateObj) {
return String(dateObj.getFullYear()).substr(2);
},
yyyy: function(dateObj) {
return dateObj.getFullYear();
},
h: function(dateObj) {
return dateObj.getHours() % 12 || 12;
},
hh: function(dateObj) {
return pad(dateObj.getHours() % 12 || 12);
},
H: function(dateObj) {
return dateObj.getHours();
},
HH: function(dateObj) {
return pad(dateObj.getHours());
},
m: function(dateObj) {
return dateObj.getMinutes();
},
mm: function(dateObj) {
return pad(dateObj.getMinutes());
},
s: function(dateObj) {
return dateObj.getSeconds();
},
ss: function(dateObj) {
return pad(dateObj.getSeconds());
},
S: function(dateObj) {
return Math.round(dateObj.getMilliseconds() / 100);
},
SS: function(dateObj) {
return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
},
SSS: function(dateObj) {
return pad(dateObj.getMilliseconds(), 3);
},
a: function(dateObj, i18n) {
return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
},
A: function(dateObj, i18n) {
return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
},
ZZ: function(dateObj) {
var o = dateObj.getTimezoneOffset();
return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
}
};
var parseFlags = {
d: [twoDigits, function (d, v) {
d.day = v;
}],
M: [twoDigits, function (d, v) {
d.month = v - 1;
}],
yy: [twoDigits, function (d, v) {
var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
d.year = '' + (v > 68 ? cent - 1 : cent) + v;
}],
h: [twoDigits, function (d, v) {
d.hour = v;
}],
m: [twoDigits, function (d, v) {
d.minute = v;
}],
s: [twoDigits, function (d, v) {
d.second = v;
}],
yyyy: [fourDigits, function (d, v) {
d.year = v;
}],
S: [/\d/, function (d, v) {
d.millisecond = v * 100;
}],
SS: [/\d{2}/, function (d, v) {
d.millisecond = v * 10;
}],
SSS: [threeDigits, function (d, v) {
d.millisecond = v;
}],
D: [twoDigits, noop],
ddd: [word, noop],
MMM: [word, monthUpdate('monthNamesShort')],
MMMM: [word, monthUpdate('monthNames')],
a: [word, function (d, v, i18n) {
var val = v.toLowerCase();
if (val === i18n.amPm[0]) {
d.isPm = false;
} else if (val === i18n.amPm[1]) {
d.isPm = true;
}
}],
ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) {
var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
if (parts) {
minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
}
}]
};
parseFlags.DD = parseFlags.DD;
parseFlags.dddd = parseFlags.ddd;
parseFlags.Do = parseFlags.dd = parseFlags.d;
parseFlags.mm = parseFlags.m;
parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
parseFlags.MM = parseFlags.M;
parseFlags.ss = parseFlags.s;
parseFlags.A = parseFlags.a;
// Some common format strings
fecha.masks = {
'default': 'ddd MMM dd yyyy HH:mm:ss',
shortDate: 'M/D/yy',
mediumDate: 'MMM d, yyyy',
longDate: 'MMMM d, yyyy',
fullDate: 'dddd, MMMM d, yyyy',
shortTime: 'HH:mm',
mediumTime: 'HH:mm:ss',
longTime: 'HH:mm:ss.SSS'
};
/***
* Format a date
* @method format
* @param {Date|number} dateObj
* @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
*/
fecha.format = function (dateObj, mask, i18nSettings) {
var i18n = i18nSettings || fecha.i18n;
if (typeof dateObj === 'number') {
dateObj = new Date(dateObj);
}
if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
throw new Error('Invalid Date in fecha.format');
}
mask = fecha.masks[mask] || mask || fecha.masks['default'];
return mask.replace(token, function ($0) {
return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
});
};
/**
* Parse a date string into an object, changes - into /
* @method parse
* @param {string} dateStr Date string
* @param {string} format Date parse format
* @returns {Date|boolean}
*/
fecha.parse = function (dateStr, format, i18nSettings) {
var i18n = i18nSettings || fecha.i18n;
if (typeof format !== 'string') {
throw new Error('Invalid format in fecha.parse');
}
format = fecha.masks[format] || format;
// Avoid regular expression denial of service, fail early for really long strings
// https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
if (dateStr.length > 1000) {
return false;
}
var isValid = true;
var dateInfo = {};
format.replace(token, function ($0) {
if (parseFlags[$0]) {
var info = parseFlags[$0];
var index = dateStr.search(info[0]);
if (!~index) {
isValid = false;
} else {
dateStr.replace(info[0], function (result) {
info[1](dateInfo, result, i18n);
dateStr = dateStr.substr(index + result.length);
return result;
});
}
}
return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});
if (!isValid) {
return false;
}
var today = new Date();
if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
dateInfo.hour = +dateInfo.hour + 12;
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0;
}
var date;
if (dateInfo.timezoneOffset != null) {
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
} else {
date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
}
return date;
};
return fecha;
})();
// Const DAY_DURATION
var DAY_DURATION = 86400000;
// Const DEFAULT_TIME_FORMAT
var DEFAULT_TIME_FORMAT = 'HH:mm:ss';
// Const DEFAULT_TIME_VALUE
var DEFAULT_TIME_VALUE = '00:00:00';
// Const DATE_PANEL_Z_INDEX
var DATE_PANEL_Z_INDEX = 2008;
// Const TIME_PANEL_Z_INDEX
var TIME_PANEL_Z_INDEX = DATE_PANEL_Z_INDEX + 1;
// Const TIME_PANEL_WIDTH
var TIME_PANEL_WIDTH = 154;
// Const KEY_CODE_ENTER
var KEY_CODE_ENTER = 13;
// Const CLASS_PLACEMENT_LEFT_BOTTOM
var CLASS_PLACEMENT_LEFT_BOTTOM = 'placement-left-bottom';
// Const CLASS_PLACEMENT_CENTER_BOTTOM
var CLASS_PLACEMENT_CENTER_BOTTOM = 'placement-center-bottom';
// Const CLASS_PLACEMENT_RIGHT_BOTTOM
var CLASS_PLACEMENT_RIGHT_BOTTOM = 'placement-right-bottom';
// Const IE_MODE
var inBrowser = typeof window !== 'undefined';
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var IE_MODE = document.documentMode;
var DatePicker = function ($el, options) {
var datepicker = this;
var core = {
defaults: {
readonly: false,
disabled: false,
type: 'date', // year/month/date/date-range/datetime/datetime-range
format: 'yyyy-MM-dd',
placeholder: 'Please pick a day',
align: 'left',
startDate: null,
endDate: null,
lang: 'en-US',
rangeSeparator: '-',
weekStart: 0,
defaultValue: '',
zIndex: DATE_PANEL_Z_INDEX,
onChange: null,
onShow: null,
onHide: null
},
_init: function () {
// copy property from options
datepicker = $.extend(true, datepicker, core.defaults, options || {});
if (datepicker.lang) datepicker = $.extend(datepicker, $.fn.datepicker.lang[datepicker.lang]);
var type = datepicker.type;
if ((type === 'datetime' || type === 'datetime-range')
&& datepicker.format === core.defaults.format) {
datepicker.format = 'yyyy-MM-dd HH:mm:ss';
} else if (type === 'year') {
datepicker.format = 'yyyy';
} else if (type === 'month' && datepicker.format === core.defaults.format) {
datepicker.format = 'yyyy-MM';
}
// init param date and value
datepicker.date = new Date();
datepicker.value = '';
datepicker.yearLabel = datepicker.date.getFullYear();
datepicker.monthLabel = datepicker.date.getMonth();
if (datepicker.readonly) $el.attr('readonly', true);
if (datepicker.disabled) $el.attr('disabled', true);
if (datepicker.placeholder) $el.attr('placeholder', datepicker.placeholder);
core._created();
},
_created: function () {
var type = datepicker.type;
var align = datepicker.align;
var zIndex = datepicker.zIndex;
var originX = align === 'center' ? '50%' : align === 'right' ? '100%' : '0';
var $body = $('body');
var dateDom;
if (type === 'date' || type === 'month' || type === 'year' || type === 'datetime') {
if (type === 'date' || type === 'datetime') {
datepicker.currentView = 'dateView';
} else if (type === 'year') {
datepicker.currentView = 'yearView';
} else if (type === 'month') {
datepicker.currentView = 'monthView';
}
// get datepicker panel
dateDom = core._generateDateDOM();
} else if (type === 'date-range' || type === 'datetime-range') {
// get range datepicker panel
dateDom = core._generateRangeDateDOM();
}
// append picker panel into the dom tree
datepicker.$pickerPanel = $(dateDom).appendTo($body).css({
position: 'absolute',
zIndex: parseInt(zIndex, 10)
});
if (!isIE || (isIE && IE_MODE > 9)) {
datepicker.$pickerPanel.css({transformOrigin: originX + ' 0', msTransformOrigin: originX + ' 0'});
}
// set position for time panel
if (datepicker.$pickerPanel.find('.gmi-time-panel').length > 0) {
var $timePanel = datepicker.$pickerPanel.find('.gmi-time-panel');
$timePanel.css({
width: TIME_PANEL_WIDTH + 'px',
position: 'absolute',
left: 0,
zIndex: TIME_PANEL_Z_INDEX
});
datepicker.$timePanel = $timePanel;
}
// bind EVENT_PICK for $el
if ($.isFunction(datepicker.onChange)) {
$el.on('pick.datepicker', datepicker.onChange);
}
// set default date
core._setDate(datepicker.defaultValue);
// set table show or hide for date, datetime, year, month
switch (type) {
case 'date':
datepicker.$pickerPanel.find('.gmi-date-table').show().siblings().hide();
break;
case 'datetime':
datepicker.$pickerPanel.find('.gmi-date-table').show().siblings().hide();
break;
case 'year':
datepicker.$pickerPanel.find('.gmi-year-table').show().siblings().hide();
break;
case 'month':
datepicker.$pickerPanel.find('.gmi-month-table').show().siblings().hide();
break;
}
// push date or time value into the input elements
core._echoDateOrTimeIntoInput();
// bind events for datepicker
core._bindEvent();
},
_unCreate: function () {
var $pickPanel = datepicker.$pickerPanel;
if ($pickPanel && $pickPanel.length > 0) $pickPanel.remove();
},
_bindEvent: function () {
$(document).on('click.datepicker', function (e) {
var $target = $(e.target);
if (!$target.is($el) && $el.has($target).length <= 0) {
core._hidePickerPanel();
}
});
// when window resizing or scrolling that the panel will change its position
$(window).on('resize.datepicker', function () {
core._setDatePanelPosition();
}).on('scroll.datepicker', function () {
core._setDatePanelPosition();
});
// bind events for $el
$el.on('focus.datepicker', core._elFocusHandler)
.on('click.datepicker', core._elClickHandler)
.on('change.datepicker', core._elChangeHandler)
.on('keyup.datepicker', core._elKeyUpHandler);
if ($.isFunction(datepicker.onShow)) {
$el.on('show.datepicker', datepicker.onShow);
}
if ($.isFunction(datepicker.onHide)) {
$el.on('show.datepicker', datepicker.onHide);
}
// cancel bubble for $pickerPanel
datepicker.$pickerPanel.on('click.datepicker', function (e) {
e.stopPropagation();
});
// bind events for time input
datepicker.$pickerPanel.on('focus.datepicker', '.gmi-time-picker--input', function (e) {
var $self = $(this);
core._setTimeView($self, e);
}).on('keyup.datepicker', '.gmi-time-picker--input', function (e) {
var $self = $(this);
core._setTimeView($self, e);
}).on('change.datepicker', '.gmi-time-picker--input', function (e) {
var $self = $(this);
core._setTimeView($self, e);
});
if (datepicker.$timePanel && datepicker.$timePanel.length > 0) {
datepicker.$timePanel.on('mouseenter.datepicker', '.gmi-time-panel__body__item', function (e) {
// mouseenter
var $self = $(this);
var $spinner = $self.find('> ul.gmi-time-panel__body__item--spinner');
$self.css('overflow', 'auto');
$spinner.css('width', '100%');
}).on('mouseleave.datepicker', '.gmi-time-panel__body__item', function (e) {
// mouseleave
var $self = $(this);
var $spinner = $self.find('> ul.gmi-time-panel__body__item--spinner');
var selfWidth = $self.outerWidth();
$self.css('overflow', 'hidden');
$spinner.css('width', selfWidth + 'px');
});
datepicker.$timePanel.on('click.datepicker', '.gmi-time-panel__body__item--spinner__item:not(.disabled)', function (e) {
var $timeItem = $(this);
var $timeItemWrapper = $timeItem.parents('.gmi-time-panel__body__item').eq(0);
var role = $timeItem.parents('.gmi-time-panel__body__item--spinner').data('role');
var num = Number($timeItem.text());
var itemHeight = $timeItem.outerHeight();
var scrollTop = num * itemHeight;
$timeItem.addClass('active').siblings().removeClass('active');
$timeItemWrapper.scrollTop(scrollTop);
e.stopPropagation();
});
// timer panel button events
datepicker.$timePanel.on('click.datepicker', '.gmi-time-panel__btn', function (e) {
var $self = $(this);
var role = $self.data('role');
var tempDate = new Date();
var $delegateTarget = $(e.delegateTarget);
var $timeInput = $delegateTarget.siblings('.gmi-time-picker--input');
var $hourSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="hour"]');
var $minSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="min"]');
var $secSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="sec"]');
var hour = Number($hourSpinner.find('> li.active').text());
var minutes = Number($minSpinner.find('> li.active').text());
var seconds = Number($secSpinner.find('> li.active').text());
tempDate.setHours(hour, minutes, seconds, 0);
switch (role) {
case 'confirm':
$timeInput.val($.formatDate(tempDate, DEFAULT_TIME_FORMAT));
$delegateTarget.hide();
var $allTimeInput = datepicker.$pickerPanel.find('.gmi-time-picker--input').filter(function () {
return $(this).val() === '' || !$.parseDate($(this).val(), DEFAULT_TIME_FORMAT);
});
if ($allTimeInput && $allTimeInput.length === 0) datepicker.$pickerPanel.find('.gmi-picker-panel__link-btn--determine').removeClass('disabled');
break;
case 'cancel':
$delegateTarget.hide();
break;
default:
break;
}
});
}
// bind event for td
datepicker.$pickerPanel.on('click.datepicker', 'td:not(.disabled)', function (e) {
var type = datepicker.type;
var format = datepicker.format;
var $delegateTarget = $(e.delegateTarget);
var $determineButton = $delegateTarget.find('.gmi-picker-panel__link-btn--determine');
var $timeInput;
var $td = $(this);
var elValue;
e.stopPropagation();
if (type === 'date' || type === 'datetime') {
var currentView = datepicker.currentView;
var year;
var month;
if (currentView === 'dateView') { // dateView
var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
if ($td.hasClass('prev-month') || $td.hasClass('next-month')) {
if ($td.hasClass('prev-month')) {
year = datepicker.monthLabel - 1 < 0 ? datepicker.yearLabel - 1 : datepicker.yearLabel;
month = datepicker.monthLabel - 1 < 0 ? 11 : datepicker.monthLabel - 1;
} else if ($td.hasClass('next-month')) {
year = datepicker.monthLabel + 1 > 11 ? datepicker.yearLabel + 1 : datepicker.yearLabel;
month = datepicker.monthLabel + 1 > 11 ? 0 : datepicker.monthLabel + 1;
}
if (type === 'date') {
elValue = $.formatDate(new Date(year, month, date), format);
core._setDate(elValue);
} else {
elValue = $.formatDate(new Date(year, month, date));
core._setNewDateDOM($delegateTarget, year, month, date);
$delegateTarget.find('.gmi-date-table td').removeClass('current').filter(function () {
return Number($(this).data('year')) === year && Number($(this).data('month')) === month &&
Number($(this).text()) === date;
}).addClass('current');
datepicker.yearLabel = year;
datepicker.monthLabel = month;
}
} else {
year = datepicker.yearLabel;
month = datepicker.monthLabel;
if (type === 'date') {
elValue = $.formatDate(new Date(year, month, date), format);
core._setDate(elValue);
} else {
elValue = $.formatDate(new Date(year, month, date));
$delegateTarget.find('.gmi-date-table').find('td').removeClass('current');
$td.addClass('current');
}
}
if (type === 'datetime') {
$delegateTarget.find('.gmi-date-picker--input').val(elValue);
$timeInput = $delegateTarget.find('.gmi-time-picker--input');
if ($timeInput.val() === '' || !$.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT)) {
$timeInput.val(DEFAULT_TIME_VALUE);
}
$determineButton.removeClass('disabled');
return false;
}
core._hidePickerPanel();
} else if (currentView === 'yearView') { // yearView
core._setYearView($td);
} else { // monthView
core._setMonthView($td);
}
} else if (type === 'month') {
switch (datepicker.currentView) {
case 'monthView':
core._setMonthView($td);
break;
case 'yearView':
core._setYearView($td);
break;
default:
break;
}
} else if (type === 'year') {
core._setYearView($td);
} else if (type === 'date-range' || type === 'datetime-range') {
var minDate = datepicker.minDate;
var maxDate = datepicker.maxDate;
var $startDateInput = $delegateTarget.find('.gmi-date-picker--input[data-role="range-start"]');
var $startTimeInput = $delegateTarget.find('.gmi-time-picker--input[data-role="range-start"]');
var $endDateInput = $delegateTarget.find('.gmi-date-picker--input[data-role="range-end"]');
var $endTimeInput = $delegateTarget.find('.gmi-time-picker--input[data-role="range-end"]');
var value;
year = Number($td.data('year'));
month = Number($td.data('month'));
date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
if (minDate && maxDate) {
datepicker.minDate = new Date(year, month, date);
datepicker.maxDate = null;
$delegateTarget.find('.gmi-date-table td').removeClass('start-date in-range end-date');
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
if (type === 'datetime-range') {
$startDateInput.val($.formatDate(datepicker.minDate));
$determineButton.addClass('disabled');
}
} else if (minDate && !maxDate) {
if (new Date(year, month, date).getTime() < minDate.getTime()) {
datepicker.minDate = new Date(year, month, date);
$delegateTarget.find('.gmi-date-table td').removeClass('start-date in-range');
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
if (type === 'datetime-range') {
$startDateInput.val($.formatDate(datepicker.minDate));
$determineButton.addClass('disabled');
}
} else {
datepicker.maxDate = new Date(year, month, date);
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('end-date in-range');
$delegateTarget.find('.gmi-date-table td').filter(function () {
var $self = $(this);
var selfYear = Number($self.data('year'));
var selfMonth = Number($self.data('month'));
var selfDate = $self.text() === datepicker.todaySuffix ? new Date().getDate() : Number($self.text());
var rangeDate = new Date(selfYear, selfMonth, selfDate);
return !$self.hasClass('prev-month') && !$self.hasClass('next-month') &&
(rangeDate > datepicker.minDate.getTime()) &&
(rangeDate.getTime() < datepicker.maxDate.getTime());
}).addClass('in-range');
if (type === 'date-range') {
value = $.formatDate(datepicker.minDate, format) + ' '+ datepicker.rangeSeparator +' ' + $.formatDate(datepicker.maxDate, format);
core._setDate(value);
core._hidePickerPanel();
} else {
$endDateInput.val($.formatDate(datepicker.maxDate));
if ($startTimeInput.val() === '') $startTimeInput.val(DEFAULT_TIME_VALUE);
if ($endTimeInput.val() === '') $endTimeInput.val(DEFAULT_TIME_VALUE);
$determineButton.removeClass('disabled');
}
}
} else if (!minDate) {
datepicker.minDate = new Date(year, month, date);
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
if (type === 'datetime-range') {
$startDateInput.val($.formatDate(datepicker.minDate));
$determineButton.addClass('disabled');
}
}
}
});
// bind event for month label
datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__label--month', function (e) {
var $monthLabel = $(this);
var $yearLabel = $monthLabel.siblings('.gmi-date-picker__header__label--year');
var $delegateTarget = $(e.delegateTarget);
var $monthTable = $delegateTarget.find('.gmi-month-table');
var year = datepicker.yearLabel;
var month = datepicker.monthLabel;
// add current class for month table td
if (!$monthTable.find('td').removeClass('current').eq(Number(month)).hasClass('disabled')) {
$monthTable.find('td').removeClass('current').eq(Number(month)).addClass('current');
}
// change year label text
$yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
// hide month label
$monthLabel.hide();
// show month table
core._setNewMonthDOM($delegateTarget, month);
// reset picker current view, current view is month view
datepicker.currentView = 'monthView';
e.stopPropagation();
});
// bind event for year label
datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__label--year', function (e) {
if (datepicker.currentView === 'yearView') {
return false;
}
var $yearLabel = $(this);
var $monthLabel = $yearLabel.siblings('.gmi-date-picker__header__label--month');
var $delegateTarget = $(e.delegateTarget);
var year = datepicker.yearLabel;
// generate new year dom
core._setNewYearDOM($delegateTarget, year, $yearLabel);
// hide month label
$monthLabel.hide();
// show year table
datepicker.currentView = 'yearView';
e.stopPropagation();
});
// bind events for arrow-down and arrow-up
datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__icon-btn', function (e) {
var $delegateTarget = $(e.delegateTarget);
var $button = $(this);
var action = $button.data('action');
var currentView = datepicker.currentView;
switch (action) {
case 'prev':
core._setPrevButtonAction($delegateTarget, currentView);
break;
case 'next':
core._setNextButtonAction($delegateTarget, currentView);
break;
default:
break;
}
e.stopPropagation();
});
// bind event for link button
datepicker.$pickerPanel.on('click.datepicker', '.gmi-picker-panel__link-btn:not(.gmi-time-panel__btn)', function (e) {
var $linkButton = $(this);
var $delegateTarget = $(e.delegateTarget);
var role = $linkButton.data('role');
var type = datepicker.type;
var dateValue;
var timeValue;
var date;
var time;
var minDateValue;
var minTimeValue;
var minDate;
var minTime;
var minValue;
var maxDateValue;
var maxTimeValue;
var maxDate;
var maxTime;
var maxValue;
var value;
switch (role) {
case 'determine':
if ($linkButton.hasClass('disabled')) {
return false;
}
if (type === 'datetime') {
dateValue = $delegateTarget.find('.gmi-date-picker--input').val();
timeValue = $delegateTarget.find('.gmi-time-picker--input').val();
date = $.parseDate(dateValue);
time = $.parseDate(timeValue, DEFAULT_TIME_FORMAT);
date.setHours(time.getHours(), time.getMinutes(), time.getSeconds(), 0);
value = $.formatDate(date, datepicker.format);
} else if (type === 'datetime-range') {
minDateValue = $delegateTarget.find('.gmi-date-picker--input[data-role="range-start"]').val();
minTimeValue = $delegateTarget.find('.gmi-time-picker--input[data-role="range-start"]').val();
minDate = $.parseDate(minDateValue);
minTime = $.parseDate(minTimeValue !== '' ? minTimeValue : DEFAULT_TIME_VALUE, DEFAULT_TIME_FORMAT);
minDate.setHours(minTime.getHours(), minTime.getMinutes(), minTime.getSeconds(), 0);
minValue = $.formatDate(minDate, datepicker.format);
maxDateValue = $delegateTarget.find('.gmi-date-picker--input[data-role="range-end"]').val();
maxTimeValue = $delegateTarget.find('.gmi-time-picker--input[data-role="range-end"]').val();
maxDate = $.parseDate(maxDateValue);
maxTime = $.parseDate(maxTimeValue !== '' ? maxTimeValue : DEFAULT_TIME_VALUE, DEFAULT_TIME_FORMAT);
maxDate.setHours(maxTime.getHours(), maxTime.getMinutes(), maxTime.getSeconds(), 0);
maxValue = $.formatDate(maxDate, datepicker.format);
value = minValue + ' '+ datepicker.rangeSeparator +' ' + maxValue;
}
core._setDate(value);
core._hidePickerPanel();
break;
case 'now':
value = $.formatDate(new Date(), datepicker.format);
core._setDate(value);
core._hidePickerPanel();
break;
case 'clear':
core._clear();
break;
default:
break;
}
e.stopPropagation();
});
// bind events for range date button
datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-range-picker__header__icon-btn', function (e) {
var $delegateTarget = $(e.delegateTarget);
var $button = $(this);
var action = $button.data('action');
core._setRangeDateView($delegateTarget, action);
e.stopPropagation();
});
datepicker.$pickerPanel.on('mouseenter.datepicker', 'td:not(.disabled)', function (e) {
if (datepicker.type !== 'date-range' && datepicker.type !== 'datetime-range') {
return false;
}
var $delegateTarget = $(e.delegateTarget);
var $td = $(this);
var year = Number($td.data('year'));
var month = Number($td.data('month'));
var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
var currentDate = new Date(year, month, date);
var minDate = datepicker.minDate;
var maxDate = datepicker.maxDate;
if (minDate && !maxDate) {
datepicker.$minDateTarget = $delegateTarget.find('.gmi-date-table td').filter(function () {
return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
Number($(this).data('year')) === minDate.getFullYear() &&
Number($(this).data('month')) === minDate.getMonth() &&
($(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text())) === minDate.getDate();
});
if (!datepicker.$minDateTarget.hasClass('start-date')) {
datepicker.$minDateTarget.addClass('in-range start-date');
}
if (currentDate.getTime() >= minDate.getTime()) {
$delegateTarget.find('.gmi-date-table td').filter(function () {
var inRangeYear = Number($(this).data('year'));
var inRangeMonth = Number($(this).data('month'));
var inRangeDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
new Date(inRangeYear, inRangeMonth, inRangeDate).getTime() > minDate.getTime() &&
new Date(inRangeYear, inRangeMonth, inRangeDate).getTime() < new Date(year, month, date).getTime();
}).addClass('in-range');
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) {
$td.addClass('in-range end-date');
} else {
$delegateTarget.find('.gmi-date-table td').filter(function () {
var endDateYear = Number($(this).data('year'));
var endDateMonth = Number($(this).data('month'));
var endDateDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
endDateYear === year && endDateMonth === month && endDateDate === date;
}).addClass('in-range end-date');
}
}
}
e.stopPropagation();
}).on('mouseleave.datepicker', 'td:not(.disabled)', function (e) {
if (datepicker.type !== 'date-range' && datepicker.type !== 'datetime-range') {
return false;
}
var $delegateTarget = $(e.delegateTarget);
var $td = $(this);
var year = Number($td.data('year'));
var month = Number($td.data('month'));
var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
var minDate = datepicker.minDate;
var maxDate = datepicker.maxDate;
if (minDate && !maxDate) {
$delegateTarget.find('.gmi-date-table td').filter(function () {
return !$(this).hasClass('start-date') && !$(this).hasClass('end-date');
}).removeClass('in-range');
if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) {
$td.removeClass('in-range end-date');
} else {
$delegateTarget.find('.gmi-date-table td').filter(function () {
var endDateYear = Number($(this).data('year'));
var endDateMonth = Number($(this).data('month'));
var endDateDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
endDateYear === year && endDateMonth === month && endDateDate === date;
}).removeClass('in-range end-date');
}
}
e.stopPropagation();
});
},
_unBindEvent: function () {
$el.off('focus.datepicker', core._elFocusHandler);
$el.off('click.datepicker', core._elClickHandler);
$el.off('change.datepicker', core._elChangeHandler);
$el.off('keyup.datepicker', core._elKeyUpHandler);
$el.off('pick.datepicker', datepicker.onChange);
$el.off('show.datepicker', datepicker.onShow);
$el.off('hide.datepicker', datepicker.onHide);
},
_generateDateDOM: function () {
var type = datepicker.type;
var hasTimeClass = type === 'datetime' ? 'has-time' : '';
var datetimeHeaderStr = type === 'datetime' ? core._generateDatetimeHeader() : '';
var datetimeFooterStr = type === 'datetime' ? core._generateDatetimeFooter(type) : '';
var startYear = Math.floor(datepicker.date.getFullYear() / 10) * 10;
var endYear = Math.floor(datepicker.date.getFullYear() / 10) * 10 + 9;
var yearLabel = type === 'date' || type === 'month' || type === 'datetime' ? datepicker.date.getFullYear() + ' '+ datepicker.yearSuffix +'' : startYear + ' '+ datepicker.yearSuffix +'' + ' - ' + endYear + ' '+ datepicker.yearSuffix +'';
var monthLabel = datepicker.monthsShort[datepicker.date.getMonth()];
var monthDom = type === 'date' || type === 'datetime' ? '<span class="gmi-date-picker__header__label--month">'+ monthLabel +'</span>' : '';
var dateDomStr = '<div data-role="'+ type +'" class="gmi-picker-panel gmi-date-picker '+ hasTimeClass +'" style="display: none;">' +
'<div class="gmi-picker-panel__body">'
+ datetimeHeaderStr +
'<div class="gmi-picker-panel__body__header">' +
'<span class="gmi-date-picker__header__label--year">'+ yearLabel +'</span>'
+ monthDom +
'<em data-action="next" class="gmi-date-picker__header__icon-btn gmi-picker-panel__btn--next"></em>' +
'<em data-action="prev" class="gmi-date-picker__header__icon-btn gmi-picker-panel__btn--prev"></em>' +
'</div>' +
'<div class="gmi-picker-panel__body__main">'
+ core._getDateTable(datepicker.date) +
'' + core._getYearTable(datepicker.date.getFullYear()) +
'' + core._getMonthTable(datepicker.date.getMonth()) +
'</div>' +
'</div>'
+ datetimeFooterStr +
'</div>';
return dateDomStr;
},
_generateRangeDateDOM: function () {
var date = datepicker.date;
var firstDate = datepicker.minDate ? datepicker.minDate : date;
var nextDate = datepicker.minDate ? $.getNextMonth(datepicker.minDate) : $.getNextMonth(date);
var type = datepicker.type;
var hasTimeClass = type === 'datetime-range' ? 'has-time' : '';
var rangeDatetimeHeaderStr = type === 'datetime-range' ? core._generateRangeDatetimeHeader() : '';
var rangeDatetimeFooterStr = type === 'datetime-range' ? core._generateDatetimeFooter(type) : '';
var rangeDateDomStr = '<div data-role="'+ type +'" class="gmi-picker-panel gmi-date-range-picker '+ hasTimeClass +'" style="display: none;">'
+ rangeDatetimeHeaderStr +
'<div class="gmi-picker-panel__body gmi-date-range-picker__body">' +
'<div class="gmi-picker-panel__body__main f-lt">' +
'<div class="gmi-date-range-picker__body__header">' +
'<p>'+ firstDate.getFullYear() +' '+ datepicker.yearSuffix +' '+ datepicker.monthsShort[firstDate.getMonth()] +'</p>' +
'<em data-action="prev-year" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--prev gmi-date-range-picker__btn--prev-year"></em>' +
'<em data-action="prev-month" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--prev gmi-date-range-picker__btn--prev-month"></em>' +
'</div>'
+ core._getDateTable(firstDate) +
'</div>' +
'<div class="gmi-picker-panel__body__main f-rt">' +
'<div class="gmi-date-range-picker__body__header">' +
'<p>'+ nextDate.getFullYear() +' '+ datepicker.yearSuffix +' '+ datepicker.monthsShort[nextDate.getMonth()] +'</p>' +
'<em data-action="next-year" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--next gmi-date-range-picker__btn--next-year"></em>' +
'<em data-action="next-month" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--next gmi-date-range-picker__btn--next-month"></em>' +
'</div>'
+ core._getDateTable(nextDate) +
'</div>' +
'</div>'
+ rangeDatetimeFooterStr +
'</div>';
return rangeDateDomStr;
},
_generateDatetimeHeader: function () {
var datetimeHeaderStr = '<ul class="gmi-picker-panel__body__header--time">' +
'<li class="gmi-picker-panel__body__header--time__wrapper">' +
'<div class="gmi-input">' +
'<input data-role="date" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.dateInputPlaceholder +'">' +
'</div>' +
'</li>' +
'<li class="gmi-picker-panel__body__header--time__wrapper">' +
'<div data-role="date" class="gmi-input gmi-time-picker--wrapper">' +
'<input data-role="date" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.dateTimeInputPlaceholder +'">'
+ core._generateTimePickerDOM('date') +
'</div>' +
'</li>' +
'</ul>';
return datetimeHeaderStr;
},
_generateRangeDatetimeHeader: function () {
var rangeDatetimeHeaderStr = '<ul class="gmi-picker-panel__body__header--time">' +
'<li class="gmi-picker-panel__body__header--time__wrapper gmi-date-range-picker__header--time__wrapper">' +
'<div class="gmi-input">' +
'<input data-role="range-start" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.rangeStartInputPlaceholder +'">' +
'</div>' +
'<div data-role="range-start" class="gmi-input gmi-time-picker--wrapper">' +
'<input data-role="range-start" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.rangeStartTimeInputPlaceholder +'">'
+ core._generateTimePickerDOM('range-start') +
'</div>' +
'</li>' +
'<li class="gmi-picker-panel__body__header--time__wrapper gmi-date-range-picker__header--time__wrapper">' +
'<div class="gmi-input">' +
'<input data-role="range-end" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.rangeEndPlaceholder +'">' +
'</div>' +
'<div data-role="range-end" class="gmi-input gmi-time-picker--wrapper">' +
'<input data-role="range-end" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.rangeEndTimeInputPlaceholder +'">'
+ core._generateTimePickerDOM('range-end') +
'</div>' +
'</li>' +
'</ul>';
return rangeDatetimeHeaderStr;
},
_generateDatetimeFooter: function (type) {
var buttonText = type === 'datetime-range' ? datepicker.clearButton : datepicker.nowDateButton;
var buttonClass = type === 'datetime-range' ? 'gmi-picker-panel__link-btn--clear' : 'gmi-picker-panel__link-btn--now';
var buttonRole = type === 'datetime-range' ? 'clear' : 'now';
var disabledClass = type === 'datetime-range' && datepicker.minDate && datepicker.maxDate ? '' : type === 'datetime' ? '' : 'disabled';
var datetimeFooterStr = '<div class="gmi-picker-panel__footer">' +
'<a href="JavaScript:" data-role="'+ buttonRole +'" class="gmi-picker-panel__link-btn '+ buttonClass +'">'+ buttonText +'</a>' +
'<a href="JavaScript:" data-role="determine" class="gmi-picker-panel__link-btn gmi-picker-panel__link-btn--determine '+ disabledClass +'">'+ datepicker.confirmDateButton +'</a>' +
'</div>';
return datetimeFooterStr;
},
_generateTimePickerDOM: function (role) {
var timePickerStr = '<div data-role="'+ role +'" class="gmi-time-panel gmi-time-picker" style="display: none;">' +
'<div class="gmi-time-panel__body">' +
'<div class="gmi-time-panel__body__item">'
+ core._getTimeSpinner('hour') +
'</div>' +
'<div class="gmi-time-panel__body__item">'
+ core._getTimeSpinner('min') +
'</div>' +
'<div class="gmi-time-panel__body__item">'
+ core._getTimeSpinner('sec') +
'</div>' +
'</div>' +
'<div class="gmi-picker-panel__footer gmi-time-panel__footer">' +
'<a href="JavaScript:" data-role="cancel" class="gmi-picker-panel__link-btn gmi-time-panel__btn gmi-picker-panel__link-btn--default">'+ datepicker.cancelTimeButton +'</a>' +
'<a href="JavaScript:" data-role="confirm" class="gmi-picker-panel__link-btn gmi-time-panel__btn gmi-picker-panel__link-btn--primary">'+ datepicker.confirmDateButton +'</a>' +
'</div>' +
'</div>';
return timePickerStr;
},
_getDateRows: function (nowDate) {
var tableRows = [ [], [], [], [], [], [] ];
var year = nowDate.getFullYear();
var month = nowDate.getMonth();
var date = new Date(year, month, 1);
var day = $.getFirstDayOfMonth(date);// day of first day
var dateCountOfMonth = $.getTotalDayCountOfMonth(date.getFullYear(), date.getMonth());
var dateCountOfLastMonth = $.getTotalDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1));
day = (day === 0) ? 7 : day;
var offset = -1 * datepicker.weekStart;
var rows = tableRows;
var count = 1;
var firstDayPosition;
var startDate = $.getStartDateOfMonth(year, month);
var now = $.clearHours(new Date()); // get now time
for (var i = 0; i < 6; i++) {
var row = rows[i];
for (var j = 0; j < 7; j++) {
var cell = row[j];
if (!cell) {
cell = { row: i, column: j, type: 'normal', year: date.getFullYear(), month: date.getMonth(), inRange: false, start: false, end: false}; // init cell
}
cell.type = 'normal';
var index = i * 7 + j;
var time = startDate.getTime() + DAY_DURATION * (index - offset);
cell.inRange = time >= $.clearHours(datepicker.minDate) && time <= $.clearHours(datepicker.maxDate);
cell.start = datepicker.minDate && time === $.clearHours(datepicker.minDate);
cell.end = datepicker.maxDate && time === $.clearHours(datepicker.maxDate);
cell.disabled = fal