/**
cp.datepicker.js provides an JavaScript Date picker control.
*/
var cp = cp || {};
$(document).ready(function() {
cp.datetimepicker = (function($) {
// These are the publicly-accessible methods
if (!$) { return; }
return {
createTimePicker: createTimePicker,
createDatePicker: createDatePicker,
};
// getOptionsFromHtmlAttributes parses through any attribute in the user-specific HTML date or time picker and
// overwrites the values in "options" with values corresponding to "data-cp-datetimepicker-{optionName}". The
// "optionName" portion of the attribute should correspond to a property of the pickadate or pickatime
// options element.
function getOptionsFromHtmlAttributes($inputElement, originalOptions) {
$($inputElement[0].attributes).each(function() {
if (this.name.match('^data-cp-datetimepicker-')) {
var optionName = this.name.replace('data-cp-datetimepicker-', '');
var originalValue = originalOptions[optionName];
if (!originalValue) {
// We only want to set the option value if it was not already set in the JavaScript options.
// That is, the JavaScript options should always win over the HTML5 options.
var optionVal = this.value;
if (parseInt(optionVal)) {
optionVal = parseInt(optionVal);
}
originalOptions[optionName] = optionVal;
}
}
});
return originalOptions;
}
// createTimePicker creates a new time picker object out of the DOM object specified by timeTextboxSelector.
// The timeTextboxSelector object must be an HTML INPUT element.
function createTimePicker(timeTextboxSelector, options) {
// The actualy Input Time component will be a hidden field. The DOM object in the source HTML
// will be the editable text field for the user.
options = options || {};
var $inputTextBox = $(timeTextboxSelector);
if (!($inputTextBox) || $inputTextBox.length === 0) {
// We got passed a bad selector
console.log('ERR: cp.datetimepicker :: Could not find element: ' + timeTextboxSelector);
return;
}
// Set any options that were specified directly from within the HTML tag.
options = getOptionsFromHtmlAttributes($inputTextBox, options);
//Wrap fieldSetSelector inside div if wrapInsideDiv is true
var fieldSetSelector = '
';
if (options.wrapInsideDiv) {
fieldSetSelector = '' + fieldSetSelector + '
';
}
$fieldset = $(fieldSetSelector);
$inputTextBox.wrap($fieldset);
var $hiddenTimeInput = $(' ');
$hiddenTimeInput.attr('data-cp-time-textbox-selector', timeTextboxSelector);
$hiddenTimeInput.attr('aria-hidden', true);
$hiddenTimeInput.insertAfter($inputTextBox);
// We need to force "editable" in the options, otherwise things start to break.
options.editable = true;
$hiddenTimeInput.pickatime(options);
var pickerObject = $hiddenTimeInput.pickatime('picker'); // reference to the pickatime object
$inputTextBox.on({
change: function() {
var inputTime = this.value;
pickerObject.set('customSelect', insertColonIfOverFourChars(inputTime), options);
},
focus: function() {
pickerObject.open(false);
// We have to set the tab-index of the "clear" button, so tabbing through fields works properly.
$('.picker__button--clear').attr('tabindex', -1);
},
blur: function() {
pickerObject.close(true);
}
});
pickerObject.on('set', function(e) {
var inputVal = $inputTextBox.val();
var selectedVal = autoTimeFormat(inputVal);
if (typeof e.clear === "object" || (typeof e.select !== "undefined" && !isNaN(e.select)) || (e.customSelect && !selectedVal.match(/^([1-9]|1[012])(:[0-5]\d) [APap][mM]$/))) {
selectedVal = this.get('value') === '' ? '12:00 AM' : this.get('value');
}
$inputTextBox.val(selectedVal);
$inputTextBox.attr('value', selectedVal);
});
// Priming initialization if there was a value specified for the input
if ($inputTextBox.val() !== '') {
pickerObject.set('select', $inputTextBox.val(), options);
}
$(pickerObject.$root).find('.picker__list').attr('aria-label', 'Time List');
return pickerObject;
}
// createDatePicker creates a new date picker object using the element specified by "dateTextboxSelector".
// The dateTextboxSelector object must be an HTML INPUT element.
function createDatePicker(dateTextboxSelector, options) {
// The actualy Input Date component will be a hidden field. The DOM object in the source HTML
// will be the editable text field for the user.
options = options || {};
var $inputTextBox = $(dateTextboxSelector);
if (!($inputTextBox) || $inputTextBox.length === 0) {
// We got passed a bad selector
console.log('ERR: cp.datetimepicker :: Could not find element: ' + dateTextboxSelector);
return;
}
// Set any options that were specified directly from within the HTML tag.
options = getOptionsFromHtmlAttributes($inputTextBox, options);
var dateAsString = $inputTextBox.val();
if (options.format == 'dd/mm/yyyy' && dateAsString.length > 0) {
var splitDate = autoCompleteDate(dateAsString, options.format).split('/');
$inputTextBox.val(splitDate[1] + '/' + splitDate[0] + '/' + splitDate[2]);
}
if (options.useNativeControls) {
$inputTextBox.prop('type', 'date');
return;
}
var $hiddenDateInput = $(' ');
$hiddenDateInput.attr('data-cp-date-textbox-selector', dateTextboxSelector);
$hiddenDateInput.attr('aria-hidden', true);
var fieldsetSelector = ' ';
if (options.wrapInsideDiv)
fieldsetSelector = '' + fieldsetSelector + '
';
$fieldset = $(fieldsetSelector);
$fieldset.attr("data-cp-datetimepicker-selector", dateTextboxSelector);
$inputTextBox.wrap($fieldset);
$hiddenDateInput.insertAfter($inputTextBox);
// We have to force the editable option, otherwise things start to break.
options.editable = true;
$hiddenDateInput.pickadate(options);
var pickerObject = $hiddenDateInput.pickadate('picker'); // reference to the pickadate object
// Add handler to make sure datepicker is visible when shown
pickerObject && pickerObject.on('open', function() {
// Add a slight delay to ensure that the flyout has been added to the dom and the flyoutHeight is nonzero
setTimeout(function() {
// Get the position of the datepicker flyout relative to the top of the document
var flyoutTop = pickerObject.$holder[0].getBoundingClientRect().top;
// Get the height of the datepicker flyout
var flyoutHeight = pickerObject.$holder[0].offsetHeight;
// Get the height of the viewport
var viewportHeight = window.innerHeight;
// Calculate the amount of scrolling needed to make the entire datepicker flyout visible
var scrollAmount = flyoutTop + flyoutHeight - viewportHeight;
// If the datepicker flyout is not fully visible, scroll the viewport down to make it visible
if (scrollAmount > 0) {
window.scrollTo(0, scrollAmount + window.scrollY);
}
}, 50);
});
function focusInput() {
$inputTextBox.off("focus", openPicker);
$inputTextBox.focus();
$inputTextBox.on("focus", openPicker);
}
pickerObject.on('close', function() {
//Trigger the change event on the picker object's element.
window.parent.$(dateTextboxSelector).change();
focusInput();
});
// setDate is a helper function that should only be called from within this module.
function setDate(dateAsString, placeholder) {
if (dateAsString === '') {
pickerObject.set('clear', null, null);
} else {
if (pickerObject.component.settings.format == 'dd/mm/yyyy') {
var splitDate = autoCompleteDate(dateAsString, pickerObject.component.settings.format).split('/');
var date = splitDate[1] + '/' + splitDate[0] + '/' + splitDate[2];
var epochDate = Date.parse(date);
} else {
var epochDate = Date.parse(autoCompleteDate(dateAsString, pickerObject.component.settings.format));
}
if (!epochDate) {
return;
} else {
var parsedDate = new Date(epochDate);
pickerObject.set('select',
[parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate()]);
}
}
}
var intervalID;
//Moves the date picker further up the DOM so it isn't overlapped https://civicplus.tpondemand.com/entity/22312
function adjustDTPicker() {
if (intervalID === 0) {
return;
}
var elemPicker = $('.picker--opened')[0];
if (elemPicker) {
$(elemPicker).appendTo('#cpDatePickerWrapper');
elemPicker.style.fontSize = 'inherit';
}
clearInterval(intervalID);
$('.cpDatePickerElevate').each(function() {
var $dateInput = $(this);
if ($dateInput.is(':focus')) {
adjustFlyoutPosition($dateInput, $('#cpDatePickerWrapper'));
}
});
intervalID = 0;
}
if ($('#cpDatePickerWrapper').length > 0) {
$(window).resize(function() {
$('.cpDatePickerElevate').each(function() {
var $dateInput = $(this);
if ($dateInput.is(':focus')) {
adjustFlyoutPosition($dateInput, $('#cpDatePickerWrapper'));
}
});
});
}
function openPicker() {
// We have to set the tab-index of the "today", "close", and "clear" buttons, so tabbing through fields works properly.
$('.picker__button--close').attr('tabindex', -1);
$('.picker__button--today').attr('tabindex', -1);
$('.picker__button--clear').attr('tabindex', -1);
pickerObject.$holder.find('[tabindex="-1"]').attr('tabindex', 0)
pickerObject.open(false);
if ($('#cpDatePickerWrapper').length > 0)
intervalID = setInterval(adjustDTPicker, 50);
}
$inputTextBox.on({
change: function() {
setDate(this.value);
},
focus: openPicker,
click: openPicker,
keydown: function(e) {
if (e.keyCode === 9) {
pickerObject.$holder.find('[tabindex="0"]').attr('tabindex', -1);
}
},
blur: function(e) {
pickerObject.close(true);
},
});
pickerObject.on('set', function() {
$inputTextBox.val(this.get('value'));
$inputTextBox.attr('value', this.get('value'));
});
// Priming initialization if there was a value specified for the input
if ($inputTextBox.val() !== '') {
setDate($inputTextBox.val());
}
return pickerObject;
}
function insertColonIfOverFourChars(inputString) {
if (inputString != null && inputString.length > 3 && inputString.indexOf(":") < 0) {
inputString = inputString.slice(0, 2) + ":" + inputString.slice(2, inputString.length);
}
return inputString;
}
function autoCompleteDate(dateAsString, formatString) {
try {
var fdts = null;
var currentDate = new Date();
//guard clauses
if (formatString == null || formatString.length === 0) {
formatString === "mm/dd/yyyy";
}
if ((dateAsString == null || dateAsString.length == 0 || dateAsString.indexOf(".") != -1) && formatString.toLowerCase == "dd/mm/yyyy") {
return ('0' + currentDate.getDate()).slice(-2) + '/' + ('0' + (currentDate.getMonth() + 1)).slice(-2) + '/' + currentDate.getFullYear();
} else if (dateAsString == null || dateAsString.length == 0 || dateAsString.indexOf(".") != -1) {
return ('0' + (currentDate.getMonth() + 1)).slice(-2) + '/' + ('0' + currentDate.getDate()).slice(-2) + '/' + currentDate.getFullYear();
}
fdts = dateAsString.split("/");
if (fdts != null) {
//switch mm/dd/yyyy to be dd/mm/yyyy for logic further down
if (formatString.toLowerCase() !== "dd/mm/yyyy") {
var day = fdts[1];
var month = fdts[0];
fdts[0] = day;
fdts[1] = month;
}
if (fdts[0] == null || fdts[0] === '0' || fdts[0].length < 1 || fdts[0].length > 2) {
fdts[0] = currentDate.getDate();
}
if (fdts[1] == null || fdts[1] === '0' || fdts[1].length < 1 || fdts[1].length > 2) {
fdts[1] = currentDate.getMonth() + 1;
}
if (fdts[2] == null || (fdts[2].length !== 2 && fdts[2].length !== 4)) {
//For last year's date it also adds time at the end of date so extracting year
fdts[2] = !isNaN(fdts[2].substr(0, 4)) ? fdts[2].substr(0, 4) : currentDate.getFullYear();
} else if (fdts[2].length === 2) {
var yr = fdts[2];
var crDate = new Date();
var crCtry = crDate.getFullYear();
var pstCnry = crCtry - 100;
crCtry = crCtry + ' ';
pstCnry = pstCnry + ' ';
crCtry = crCtry.substr(0, 2);
pstCnry = pstCnry.substr(0, 2);
if (yr >= 32)
yr = pstCnry + yr;
else
yr = crCtry + yr;
fdts[2] = yr;
}
}
if (formatString.toLowerCase() === "dd/mm/yyyy") {
dateAsString = ('0' + fdts[0]).slice(-2) + '/' + ('0' + fdts[1]).slice(-2) + '/' + fdts[2];
} else {
dateAsString = ('0' + fdts[1]).slice(-2) + '/' + ('0' + fdts[0]).slice(-2) + '/' + fdts[2];
}
return dateAsString;
} catch (e) {
return "";
}
}
function autoTimeFormat(inputString) {
const regex1 = /^(0?[1-9]|1[0-2]) ?([ap]m)$/i; //eg: 5AM -> 5:00 AM
const regex2 = /^(\d{1,2})(\d{2}) ?([ap]m)$/i; //eg: 500AM -> 5:00 AM
const regex3 = /^(\d{1,2})(?::(\d{2}))?\s*([ap]m)?$/i; //eg: 05:00 am or 5:00 am -> 5:00 AM
if (regex1.test(inputString)) {
return inputString.replace(regex1, (match, hour, period) => {
const hourNum = parseInt(hour);
const formattedHour = hourNum === 12 ? 12 : hourNum % 12; // Convert to 12-hour format
const formattedMinute = "00"; // Add 00 as minutes
const formattedPeriod = period.toUpperCase(); // Convert period to uppercase
return `${formattedHour}:${formattedMinute} ${formattedPeriod}`;
});
}
else if (regex2.test(inputString)) {
return inputString.replace(regex2, (match, hour, minute, period) => {
const hourNum = parseInt(hour);
const formattedHour = hourNum === 12 ? 12 : hourNum % 12; // Convert to 12-hour format
const formattedMinute = minute.padStart(2, "0"); // Pad minute with zero if necessary
const formattedPeriod = period.toUpperCase(); // Convert period to uppercase
return `${formattedHour}:${formattedMinute} ${formattedPeriod}`;
});
}
else if (regex3.test(inputString)) {
return inputString.replace(regex3, (match, hour, minute, period) => {
const hourNum = parseInt(hour);
const formattedHour = hourNum === 12 ? 12 : hourNum % 12; // Convert to 12-hour format
const formattedMinute = minute ? minute : "00"; // Set minute to "00" if not provided
const formattedPeriod = period ? period.toUpperCase() : ""; // Convert period to uppercase if provided
return `${formattedHour}:${formattedMinute} ${formattedPeriod}`;
});
}
else {
return inputString;
}
}
})(window.FeatureToggles.isActive("CMS.JqueryUpgrade.UpgradeTo224") && $('#hdnModuleEligibleForJquery224Upgrade').val() == "true" ? jQuery : typeof jQuery182 === "function" ? jQuery182 : null );
});