Files
thetool/public/js/timepicker-bs4.js
2024-11-12 18:31:21 +01:00

965 lines
33 KiB
JavaScript

/**
* Time picker for Bootstrap 4
*
* https://github.com/lesilent/timepicker-bs4
*/
(function () {
//-------------------------------------
'use strict';
/**
* Array of dayjs format substrings,
*
* 0 = used for regex to determine whether format contains unit
* 1 = format used for buttons and inputs
* 2 = the unit/view name
*
* @type {array}
*/
let FORMATS = [
['h', 'h', 'hour'],
['m', 'mm', 'minute'],
['s', 'ss', 'second'],
['a', 'A', 'meridiem'],
];
/**
* Unit lengths
*
* @type {object}
*/
const UNIT_LENGTHS = { hour: 24, minute: 60, second: 60, meridiem: 2 };
/**
*
* @type {string}
*/
const ACTIVE_CLASS = 'active btn-info';
/**
*
* @type {string}
*/
const INACTIVE_CLASS = 'btn-outline-dark border-white';
/**
* Flag for whether plugin has been initialized
*
* @type {boolean}
*/
let initialized = false;
/**
* Parse a time string and return a dayjs object
*
* @param {string} str
* @param {object} options
* @return {object|boolean} either a dayjs object or false on error
*/
function parseTime(str, options)
{
let input_time = false, matches;
if (typeof str == 'string')
{
str = str.replace(/^\s+|\s+$/g, '');
if ((matches = str.match(/^([0-2]?\d)(?:s*:\s*([0-5]\d))?(?:\s*:\s*([0-5]\d))?(?:\s*([AP])\.?(?:M\.?)?)?$/i))
&& parseInt(matches[1]) > (matches[4] ? 0 : -1) && parseInt(matches[1]) < (matches[4] ? 13 : 24)
&& (matches[2] === undefined || (parseInt(matches[2]) > -1 && parseInt(matches[2]) < 60))
&& (matches[3] === undefined || (parseInt(matches[3]) > -1 && parseInt(matches[3]) < 60)))
{
let hour = parseInt(matches[1]);
if (matches[4])
{
hour = hour % 12 + ((matches[4].toUpperCase() == 'P') ? 12 : 0);
}
input_time = dayjs().hour(hour).minute(matches[2] == undefined ? 0 : parseInt(matches[2])).second((matches[3] === undefined) ? 0 : parseInt(matches[3]));
}
else
{
input_time = (options && options.format)
? dayjs(str, options.format)
: dayjs(str);
}
}
else
{
input_time = dayjs(str);
}
return (input_time && input_time.isValid()) ? input_time : false;
}
/**
* Return allowed unit text object based on min time, max time, and step
*
* @param {object} options
* @return {object}
*/
function getUnitText(options)
{
const minTime = options.minTime || dayjs().startOf('day');
const maxTime = options.maxTime || dayjs().endOf('day');
const step = options.step || 60;
let valid = { offset: {}, hour: {}, minute: {}, second: {}, meridiem: {}, length: 0 };
let iTime = minTime.clone();
const unixOffset = minTime.startOf('day').unix();
while (iTime.isBefore(maxTime) || iTime.isSame(maxTime, 'second'))
{
valid.offset[iTime.unix() - unixOffset] = true;
valid.hour[iTime.hour()] = true;
valid.minute[iTime.minute()] = true;
valid.second[iTime.second()] = true;
valid.meridiem[(iTime.hour() > 11) ? 1 : 0] = true;
valid.length++;
iTime = iTime.add(step, 'second');
}
// Convert valid units to arrays
let unitText = { hour: [], minute: [], second:[], meridiem: [], length: valid.length };
for (let i = 0; i < 24; i++)
{
unitText.hour.push((i in valid.hour)
? ((i == 0 || i == 12) ? 12 : i % 12)
: null);
}
['minute', 'second'].forEach(function (field) {
for (let i = 0; i < 60; i++)
{
unitText[field].push((i in valid[field]) ? ((i < 10) ? '0' + i : i) : null);
}
});
unitText.meridiem.push((0 in valid.meridiem) ? 'AM' : null);
unitText.meridiem.push((1 in valid.meridiem) ? 'PM' : null);
/*
// Craate position arrays
for (let i = 1; i < 13; i++)
{
position.hour[i] = ((i % 12) in valid.hour || (i % 12 + 12) in valid.hour);
position.minute[i] = (i * 5 % 60) in valid.minute;
position.second[i] = (i * 5 % 60) in valid.second;
position.meridiem[i] = (i % 6 > 0) ? (((i < 6) ? 1 : 0) in valid.meridiem) : false;
}
*/
return unitText;
}
/**
* Return whether format string contains a token string
*
* This removes escaped characters from format string prior searching for token
*
* @param {string} format
* @param {string} searchElement
* @return {boolean}
*/
function hasFormat(format, searchElement)
{
return (format.replace(/\[[^\]]*\]/g).indexOf(searchElement) >= 0);
}
/**
* Update the view
*
* @param {object} $input the input object
*/
function updateView($input)
{
const prevView = $input.data('prevview');
const view = $input.data('view') || 'hour';
const viewTime = $input.data('viewtime');
const options = $input.data('options');
const clock_24 = hasFormat(options.format, 'H');
const step = options.step || 60;
let submit_disabled = false;
if (60 % step > 0)
{
const minTime = options.minTime || dayjs().startOf('day');
let viewOffset = viewTime.diff(viewTime.startOf('day'), 'second');
if (!hasFormat(options.format, 's'))
{
viewOffset -= (viewOffset % 60);
}
submit_disabled = ((viewOffset - minTime.diff(minTime.startOf('day'), 'second')) % step > 0);
}
const input_id = $input.attr('id');
const $content = jQuery('#' + input_id + '-picker-content').attr('data-view', view);
$content.find('.submit-btn').prop('disabled', submit_disabled
|| (options.minTime && viewTime.isBefore(options.minTime, 'second'))
|| (options.maxTime && viewTime.isAfter(options.maxTime, 'second')));
let number, position, format;
switch (view)
{
case 'hour':
number = viewTime.get(view);
position = (number % 12 > 0) ? (number % 12) : 12;
format = clock_24 ? 'H' : 'h';
if (hasFormat(options.format, format + format))
{
format += format;
}
break;
case 'minute':
case 'second':
number = viewTime.get(view);
if (number % 5 == 0)
{
position = (number > 0) ? (number / 5) : 12;
}
format = view.charAt(0).repeat(2);
break;
case 'meridiem':
position = (viewTime.hour() > 11) ? 3 : 9;
format = 'A';
break;
default:
console.warn('Invalid view ' + view);
return false;
}
const text = viewTime.format(format);
$content.find('.timepicker-btns button').removeClass('font-weight-bold').filter('[data-unit="' + view + '"]').addClass('font-weight-bold').text(text);
$content.find('.clock-input-table .chevron-btn').data('unit', view);
FORMATS[0][1] = clock_24 ? 'H' : 'h';
FORMATS.forEach(function (formats) {
const text = viewTime.format(formats[1]);
$content.find('.' + formats[2] + '-btn').text(text);
$content.find('.' + formats[2] + '-input').val(text);
});
if (view != prevView)
{
$content.find('.clock-input-table button').each(function () {
const $this = jQuery(this);
let pos = parseInt($this.attr('class').match(/\bpos\-(\d+)/)[1]);
let disabled = true;
if (pos > 0)
{
let positions;
switch (view)
{
case 'hour':
positions = [pos, pos % 12, pos % 12 + 12];
break;
case 'minute':
case 'second':
positions = [pos % 12 * 5];
break;
case 'meridiem':
positions = (pos % 6 > 0) ? [(pos < 6) ? 1 : 0] : [];
break;
}
positions.forEach(function (pos) {
disabled &&= (options.unitText[view][pos] === null);
});
$this.prop('disabled', disabled).toggleClass('text-light', disabled).toggleClass(INACTIVE_CLASS, pos != position).toggleClass(ACTIVE_CLASS, pos == position);
}
else
{
$this.text(text);
}
});
}
$content.data('prevview', view);
return true;
}
/**
* Update the clock picker in the popover
*
* @param {object} $input the input object
*/
function updatePicker($input)
{
const input_id = $input.attr('id');
const options = $input.data('options');
const now = dayjs();
const minTime = options.minTime || dayjs().startOf('day');
const maxTime = options.maxTime || dayjs().endOf('day');
const step = options.step || 60;
let validSteps = { hour: {}, minute: {}, second: {}, meridiem: {} };
let viewTime = $input.data('viewtime');
let iTime = minTime.clone();
while (iTime.isBefore(maxTime) || iTime.isSame(maxTime, 'second'))
{
validSteps.hour[iTime.hour()] = true;
validSteps.minute[iTime.minute()] = true;
validSteps.second[iTime.second()] = true;
validSteps.meridiem[(iTime.hour() > 11) ? 1 : 0] = true;
if (!iTime.isBefore(minTime) && !iTime.isAfter(maxTime))
{
if (!viewTime && now.isBefore(iTime))
{
viewTime = iTime;
}
}
iTime = iTime.add(step, 'second');
}
if (!viewTime)
{
viewTime = now.endOf(hasFormat(options.format, 's') ? 'second' : 'minute');
}
$input.data('viewtime', viewTime);
// Build html
const has_second = (hasFormat(options.format, 's') && step % 60 > 0);
const clock_24 = hasFormat(options.format, 'H');
const clock_enabled = ((step % 300) == 0) && false;
const viewHour = viewTime.hour();
let html = '<div class="clock-input' + (clock_enabled ? '' : ' d-none') + '">'
+ '<div class="d-flex justify-content-center align-items-center timepicker-btns">'
+ '<div class="btn-group">'
+ '<button type="button" class="btn px-2 font-weight-bold hour-btn" data-unit="hour">' + viewTime.format('hh') + '</button>'
+ '<a class="btn px-0 disabled" href="javascript:void(0)" role="button" aria-disabled="true">:</a>'
+ '<button type="button" class="btn px-2 minute-btn" data-unit="minute"' + + ((step % 3600 > 0) ? '' : ' disabled="disabled"') + '>' + viewTime.format('mm') + '</button>'
+ (has_second ? '<a class="btn px-0 disabled" href="javascript:void(0)" role="button" aria-disabled="true">:</a><button type="button" id="' + input_id + '-picker-second-btn" class="btn px-2 second-btn" data-unit="second">' + viewTime.format('ss') + '</button>' : '')
+ (clock_24 ? '<button type="button" class="btn px-2 meridiem-btn" data-unit="meridiem">' + viewTime.format('A') + '</button>' : '')
+ '</div></div>'
+ ('<table class="clock-input-table table table-sm table-borderless timepicker-table mx-auto w-auto mb-1">'
+ '<thead class="thead-light"><tr><th class="text-center py-1" colspan="5"><span class="hour">Hour</span><span class="minute">Minute</span><span class="second">Second</span><span class="meridiem">Meridiem</span></th></tr></thead>'
+ '<tbody>'
+ '<tr>'
+ '<td></td>'
+ '<td class="text-right">{{11}}</td>'
+ '<td class="text-center">{{12}}</td>'
+ '<td class="text-left">{{1}}</td>'
+ '<td></td>'
+ '</tr><tr>'
+ '<td class="text-right">{{10}}</td>'
+ '<td class="text-center align-bottom" colspan="3"><a id="' + input_id + '-picker-add-link" class="btn btn-link px-1 mx-0 chevron-btn" data-step="1" href="javascript:void(0)"><i class="fas fa-chevron-up fa-lg"></i></a></td>'
+ '<td class="text-left">{{2}}</td>'
+ '</tr><tr>'
+ '<td class="text-right">{{9}}</td>'
+ '<td class="text-center" colspan="3">{{0}}</td>'
+ '<td class="text-left">{{3}}</td>'
+ '</tr><tr>'
+ '<td class="text-right">{{8}}</td>'
+ '<td class="text-center" colspan="3"><a id="' + input_id + '-picker-sub-link" class="btn btn-link px-1 mx-0 chevron-btn" data-step="-1" href="javascript:void(0)"><i class="fas fa-chevron-down fa-lg"></i></a></td>'
+ '<td class="text-left">{{4}}</td>'
+ '</tr><tr>'
+ '<td></td>'
+ '<td class="text-right">{{7}}</td>'
+ '<td class="text-center">{{6}}</td>'
+ '<td class="text-left">{{5}}</td>'
+ '<td></td>'
+ '</tr></tbody></table>').replace(/{{(\w+)}}/g, function (match, position) {
if (position == 0)
{
return '<button type="button" id="'+ input_id + '-picker-center-btn" class="btn btn-outline-dark border-white font-weight-bold pos-0" data-hour="' + viewHour + '"'
+ (viewHour in validSteps.hour ? '' : ' disabled="disabled"')
+ '>' + ((viewHour % 12 > 0) ? viewHour % 12 : 12) + '</button>';
}
const pos_hour = position % 12;
const pos_minute = pos_hour * 5;
const meridiem_class = (position % 3 > 0) ? ' text-light' : '';
const meridiem_text = (position % 6 > 0) ? ((position < 6) ? 'PM' : 'AM') : '&nbsp;';
return '<button type="button" class="btn px-1 '
+ ((viewHour % 12 == position % 12) ? 'active btn-info' : 'btn-outline-dark border-white')
+ ' pos-' + position + '" '
+ (pos_hour in validSteps.hour ? '' : ' disabled="disabled"')
+ '><span class="hour">' + position + '</span><span class="minute second">' + (pos_minute > 9 ? '' : '0') + pos_minute + '</span><span class="meridiem' + meridiem_class + '">' + meridiem_text
+ '</span></button>';
})
+ '</div><div class="keyboard-input' + (clock_enabled ? ' d-none' : '') + '"><table class="mx-auto text-center"><tr>'
+ '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="hour" data-step="1" href="javascript:void(0)"><i class="fas fa-chevron-up fa-lg"></i></a></td>'
+ '<td></td>'
+ '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="minute" data-step="1" href="javascript:void(0)"><i class="fas fa-chevron-up fa-lg"></i></a></td>'
+ (has_second ? '<td></td><td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="second" data-step="1" href="javascript:void(0)"><i class="fas fa-chevron-up fa-lg"></i></a></td>' : '')
+ (clock_24 ? '' : '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="meridiem" data-step="1" href="javascript:void(0)"><i class="fas fa-chevron-up fa-lg"></i></a></td>')
+ '</tr><tr>'
+ '<td><input type="text" class="form-control text-center border-light hour-input" minlength="1" maxlength="2" inputmode="numeric" /></td><td>:</td><td><input type="text" class="form-control text-center border-light minute-input" minlength="1" maxlength="2" inputmode="numeric" /></td>'
+ (has_second ? '<td>:</td><td><input type="text" class="form-control text-center border-light second-input" minlength="1" maxlength="2" inputmode="numeric" /></td>' : '')
+ (clock_24 ? '' : '<td><button type="button" class="btn meridiem-btn border-light"></button></td>')
+ '</tr><tr>'
+ '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="hour" data-step="-1" href="javascript:void(0)"><i class="fas fa-chevron-down fa-lg"></i></a></td>'
+ '<td></td>'
+ '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="minute" data-step="-1" href="javascript:void(0)"><i class="fas fa-chevron-down fa-lg"></i></a></td>'
+ (has_second ? '<td></td><td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="second" data-step="-1" href="javascript:void(0)"><i class="fas fa-chevron-down fa-lg"></i></a></td>' : '')
+ (clock_24 ? '' : '<td><a class="btn btn-link px-1 mx-0 chevron-btn" data-unit="meridiem" data-step="-1" href="javascript:void(0)"><i class="fas fa-chevron-down fa-lg"></i></a></td>')
+ '</tr></table></div>'
+ '<div class="d-flex justify-content-between">'
+ '<div class="invisible"><button type="button" class="btn btn-link input-toggle-btn' + (clock_enabled ? ' d-none' : '') + '" data-input="clock"><i class="far fa-clock fa-fw"></i></button><button type="button" class="btn btn-link input-toggle-btn' + (clock_enabled ? '' : ' d-none') + '" data-input="keyboard"><i class="far fa-keyboard fa-fw"></i></button></div>'
+ '<div><button type="button" class="btn btn-secondary mx-1 cancel-btn" data-dismiss="popover" title="Cancel"><i class="fas fa-times fa-fw"></i></button><button type="button" class="btn btn-primary mx-1 submit-btn" title="OK"><i class="fas fa-check fa-fw"></i></button></div>'
+ '</div>';
const $content = jQuery('#' + input_id + '-picker-content');
const $table = $content.html(html).find('.clock-input-table');
const $center_btn = jQuery('#' + input_id + '-picker-center-btn');
$content.parents('.timepicker-popover').attr('data-scheme', (options.scheme == 'auto') ? ((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : '') : options.scheme);
$content.find('.timepicker-btns button').on('click', function () {
const unit = jQuery(this).blur().data('unit');
$content.find('.clock-input-table .chevron-btn').data('unit', unit);
updateView($input.data('view', unit));
});
const $hour_input = $content.find('.hour-input').on('change', function () {
let hour = this.value.replace(/\D+/, '');
if (hour.length > 0)
{
hour = parseInt(hour);
if (hour > -1 && hour < 24 && hour in options.unitText.hour)
{
$input.data('viewtime', $input.data('viewtime').hour(hour));
}
}
updateView($input);
});
$hour_input.add($content.find('.minute-input, .second-input').on('change', function () {
let number = this.value.replace(/\D+/, '');
if (number.length > 0)
{
number = parseInt(number);
const unit = jQuery(this).attr('class').match(/(minute|second)\-input/)[1];
if (number > -1 && number < 60 && number in options.unitText[unit])
{
$input.data('viewtime', $input.data('viewtime').set(unit, number));
}
}
updateView($input);
})).on('keyup', function (event) {
if (event.key == 'Enter')
{
$content.find('.submit-btn').triggerHandler('click');
}
});
$content.find('.meridiem-btn').on('click', function () {
$input.data('viewtime', $input.data('viewtime').hour(($input.data('viewtime').hour() + 12) % 24));
updateView($input);
}).on('keydown', function (event) {
const key = event.key.toUpperCase();
const hour = $input.data('viewtime').hour();
let offset = 0;
if (key == 'A' && hour > 12)
{
offset = -12;
}
else if (key == 'P' && hour < 11)
{
offset = 12;
}
if (offset != 0)
{
$input.data('viewtime', $input.data('viewtime').hour(hour + offset));
updateView($input);
}
});
$content.find('.input-toggle-btn').attr('tabindex', -1).on('click', function () {
const input = jQuery(this).data('input');
$content.find('.clock-input').toggleClass('d-none', input != 'clock');
$content.find('.keyboard-input').toggleClass('d-none', input != 'keyboard');
$content.find('.input-toggle-btn').each(function () {
const $this = jQuery(this);
$this.toggleClass('d-none', $this.data('input') == input);
});
$input.popover('update');
$hour_input.select();
});
$content.find('.cancel-btn').on('click', function () {
$input.popover('hide');
});
$content.find('.submit-btn').on('click', function () {
$input.val($input.data('viewtime').format($input.data('options').format)).popover('hide').data('view', null).trigger('change');
});
$table.find('button').on('click', function () {
const $this = jQuery(this);
let viewTime = $input.data('viewtime');
let position = $this.attr('class').match(/\bpos\-(\d+)/)[1];
const view = $input.data('view') || 'hour';
switch (view)
{
case 'hour':
if (position > 0)
{
viewTime = viewTime.hour(position % 12 + ((viewTime.hour() > 11) ? 12 : 0));
}
break;
case 'minute':
if (position > 0)
{
viewTime = viewTime.minute(position % 12 * 5);
}
break;
case 'second':
if (position > 0)
{
viewTime = viewTime.second(position % 12 * 5);
}
break;
case 'meridiem':
if (position > 0)
{
viewTime = viewTime.hour(viewTime.hour() % 12 + ((position < 6) ? 12 : 0));
}
break;
}
$input.data('viewtime', viewTime);
let picked = true;
FORMATS.forEach(function (formats) {
const regex = new RegExp(formats[0], 'i');
if (picked && regex.test(options.format))
{
position = null;
let number;
const nextUnit = formats[2];
switch (nextUnit)
{
case 'hour':
number = viewTime.get(nextUnit) % 12;
position = (number > 0) ? number : 12;
break;
case 'minute':
case 'second':
number = viewTime.get(nextUnit);
if (number % 5 == 0)
{
position = (number > 0) ? (number / 5) : 12;
}
break;
case 'meridiem':
position = (viewTime.hour() > 11) ? 3 : 9;
break;
}
$input.data('view', nextUnit);
picked = false;
}
});
if (picked)
{
$input.val(viewTime.format(options.format)).popover('hide').data('view', null).trigger('change');
}
else
{
updateView($input);
}
});
$content.find('.chevron-btn').attr('tabindex', -1).on('click', function () {
const $this = jQuery(this).blur();
const options = $input.data('options');
const unit = $this.data('unit') || 'hour';
const step = $this.data('step');
const viewTime = $input.data('viewtime');
const number = (unit == 'meridiem')
? ((viewTime.hour() > 11) ? 1 : 0)
: viewTime.get(unit);
if (!(number in options.unitText[unit]))
{
// Set to number
}
if (options.unitText[unit].length < 2)
{
return;
}
const unitLength = UNIT_LENGTHS[unit];
let idx = number;
switch (unit)
{
case 'hour':
case 'minute':
case 'second':
do
{
idx = (idx + step + unitLength) % unitLength;
if (options.unitText[unit][idx] !== null)
{
$center_btn.text(options.unitText[unit][idx]);
$input.data('viewtime', viewTime.set(unit, idx));
break;
}
}
while (idx != number);
break;
case 'meridiem':
idx = (idx + step + unitLength) % unitLength;
if (options.unitText[unit][idx] !== null)
{
$center_btn.text(options.unitText[unit][idx]);
$input.data('viewtime', viewTime.hour(viewTime.hour() % 12 + ((idx > 0) ? 12 : 0)));
}
break;
}
updateView($input);
});
updateView($input);
}
/**
* Add method for initializing plugin
*/
jQuery.fn.timepicker = function (options) {
// Get boostrap version
const bs_version = parseInt(((typeof bootstrap == 'object') ? bootstrap.Dropdown.VERSION : jQuery.fn.dropdown.Constructor.VERSION || '0').replace(/\..+$/, ''));
if (bs_version < 4)
{
console.error('Invalid bootstrap version ' + bs_version + ' detected');
}
// Handle functions
if (typeof options == 'string')
{
if (this.length < 1)
{
return undefined;
}
let input_options = this.data('options') || {};
const single_arg = (arguments.length == 1);
switch (options)
{
case 'format':
if (single_arg)
{
return input_options.format;
}
else if (arguments[1] && typeof arguments[1] == 'string')
{
input_options.format = arguments[1];
this.data('options', input_options);
}
else
{
console.warn('Invalid format');
}
break;
case 'defaultTime':
case 'minTime':
case 'maxTime':
if (single_arg)
{
return input_options[options];
}
else if (arguments[1])
{
let newTime = parseTime(arguments[1]);
if (newTime && newTime.isValid())
{
if (options == 'defaultTime')
{
if (input_options.minTime && newTime.isBefore(input_options.minTime))
{
newTime = false;
console.warn('defaultTime is before minTime');
}
else if (input_options.maxTime && newTime.isAfter(input_options.maxTime))
{
newTime = false;
console.warn('defaultTime is after maxTime');
}
}
if (newTime)
{
input_options[options] = newTime;
input_options.unitText = getUnitText(input_options);
this.data('options', input_options);
}
}
else
{
console.warn('Invalid ' + options);
}
}
else
{
input_options[options] = null;
input_options.unitText = getUnitText(input_options);
this.data('options', input_options);
}
break;
case 'step':
if (single_arg)
{
return input_options[options];
}
else if (arguments[1])
{
const step = parseInt(arguments[1]);
if (step > 0 && step < 86400
&& step % (hasFormat(input_options.format, 's') ? 1 : 60) == 0)
{
input_options.step = step;
input_options.unitText = getUnitText(input_options);
this.data('options', input_options);
}
else
{
console.warn('Invalid ' + options);
}
}
else
{
input_options.step = 60;
input_options.unitText = getUnitText(input_options);
this.data('options', input_options);
}
break;
case 'scheme':
if (single_arg)
{
return input_options.scheme;
}
else if (arguments[1] === null || typeof arguments[1] == 'string')
{
input_options.scheme = arguments[1];
this.data('options', input_options);
}
else
{
console.warn('Invalid scheme');
}
break;
case 'time':
if (single_arg)
{
return parseTime(this.val(), input_options) || null;
}
else
{
const newTime = (arguments[1]) ? parseTime(arguments[1], input_options) : null;
this.val((newTime && newTime.isValid()) ? newTime.format(input_options.format) : '');
}
break;
case 'viewTime':
if (single_arg)
{
return this.data('viewtime');
}
else
{
const newTime = (arguments[1]) ? parseTime(arguments[1], input_options) : null;
this.data('viewtime', newTime);
}
break;
case 'view':
if (single_arg)
{
return this.data('view');
}
else
{
const view = arguments[1];
updateView(jQuery(this).data('view', view));
}
break;
default:
break;
}
return this;
}
// Initialize code if it hasn't already
if (!initialized)
{
initialized = true;
let table_class = '.timepicker-table ';
jQuery(document.head).append('<style id="timepicker-style">'
+ '.timepicker-popover { font-size: inherit; }'
+ '.timepicker-popover .btn-link:hover { background-color: #e2e6ea; }'
+ '.timepicker-popover .meridiem-btn { min-width: 3.2rem; }'
+ '.timepicker-popover .meridiem-btn:hover { box-shadow: 0 0 0 .2rem rgba(0,123,255,.25); }'
+ '.timepicker-popover input { width: 3rem; }'
+ '.timepicker-btns .btn:hover { background-color: #e2e6ea; color: #000; }'
+ '.timepicker-content span.hour, .timepicker-content span.minute, .timepicker-content span.second, .timepicker-content span.meridiem { display: none; }'
+ '.timepicker-content[data-view="hour"] span.hour,'
+ '.timepicker-content[data-view="minute"] span.minute,'
+ '.timepicker-content[data-view="second"] span.second,'
+ '.timepicker-content[data-view="meridiem"] span.meridiem { display: inline; }'
+ table_class + 'td button:focus { box-shadow: none !important; }'
+ table_class + 'td button:not(:disabled):hover { background-color: #6c757d !important; border-color: #6c757d !important; color: #fff; }'
+ table_class + 'td button:disabled { cursor: not-allowed; }'
+ table_class + 'td button.today { background-color: #fcf8e3; }'
+ table_class + 'td, .timepicker-table th { padding: 0; }'
+ table_class + 'button.btn { min-width: 2.3rem; }'
+ table_class + 'button:focus { box-shadow: none !important; }'
+ table_class + 'button:not(:disabled):hover { background-color: #6c757d !important; border-color: #6c757d !important; color: #fff; }'
+ table_class + 'td button.pos-1 { margin: .6rem 0 0 .4rem; }'
+ table_class + 'td button.pos-2 { margin: 0 .7rem .3rem 0; }'
+ table_class + 'td button.pos-3 { margin: 0 0 0 .6rem; }'
+ table_class + 'td button.pos-4 { margin: .3rem .7rem 0 0; }'
+ table_class + 'td button.pos-5 { margin: 0 0 .6rem .4rem; }'
+ table_class + 'td button.pos-6 { margin: .8rem 0 0 0; }'
+ table_class + 'td button.pos-7 { margin: 0 .4rem .6rem 0; }'
+ table_class + 'td button.pos-8 { margin: .3rem 0 0 .7rem; }'
+ table_class + 'td button.pos-9 { margin: 0 .6rem 0 0; }'
+ table_class + 'td button.pos-10 { margin: 0 0 .3rem .7rem; }'
+ table_class + 'td button.pos-11 { margin: .6rem .4rem 0 0; }'
+ table_class + 'td button.pos-12 { margin: 0 0 .6rem 0; }'
+ '.timepicker-popover[data-scheme="dark"] { background-color: #000000; border-color: #ffffff; color: #dee2e6; }'
+ '.timepicker-popover[data-scheme="dark"] .popover-header { background-color: #343a40; color: #ffffff; }'
+ '.timepicker-popover[data-scheme="dark"] .popover-header .close { filter: invert(1) grayscale(1) brightness(2); }'
+ '.timepicker-popover[data-scheme="dark"] .border-light { background-color: #000000; border-color: #6c757d !important; color: #ffffff; }'
+ '.timepicker-popover[data-scheme="dark"] input.border-light:focus { background-color: inherit; border-color: #86b7fe !important; color: #ffffff; }'
+ '.timepicker-popover[data-scheme="dark"] .meridiem-btn { color: #ffffff; }'
+ '.timepicker-popover[data-scheme="dark"] .meridiem-btn:hover { border-color: #86b7fe !important; }'
+ '</style>');
// Make popovers close when clicked outside of them
jQuery(document.body).on('mouseup', function (e) {
if (jQuery(e.target).parents('.popover').length == 0)
{
jQuery('.timepicker').popover('hide');
}
});
}
// Process options
if (typeof options == 'undefined')
{
options = {};
}
const common_options = jQuery.extend({}, jQuery.fn.timepicker.defaults, options);
['minTime', 'maxTime'].forEach(function (option) {
if (common_options[option])
{
common_options[option] = parseTime(common_options[option]);
}
});
// Initialize the inputs
return this.each(function () {
const $input = jQuery(this);
// Get input id
let input_id = this.id;
let $toggles = $input.siblings().find('[data-toggle="timepicker"]:not([data-target])');
if (this.id)
{
$toggles = $toggles.add('[data-toggle="timepicker"][data-target="#' + this.id + '"]');
}
else
{
input_id = 'input-' + Math.floor(Math.random() * 1000000 + 1);
this.id = input_id;
}
// Process options
let input_options = jQuery.extend(true, {}, common_options);
let format = $input.data('format') || common_options.format;
if (format)
{
input_options.format = format;
}
let minTime = $input.attr('min') || $input.data('mintime') || common_options.minTime;
if (minTime && (minTime = parseTime(minTime)) && minTime.isValid())
{
input_options.minTime = minTime;
}
let maxTime = $input.attr('max') || $input.data('maxtime') || common_options.maxTime;
if (maxTime && (maxTime = parseTime(maxTime)) && maxTime.isValid())
{
input_options.maxTime = maxTime;
}
let defaultTime = $input.data('default') || common_options.defaultTime;
if (defaultTime && (defaultTime = parseTime(defaultTime)) && defaultTime.isValid()
&& !(input_options.minTime && defaultTime.isBefore(input_options.minTime))
&& !(input_options.maxTime && defaultTime.isAfter(input_options.maxTime)))
{
input_options.defaultTime = defaultTime;
}
const step = $input.attr('step') || $input.data('step') || common_options.step;
if (step > 0 && step < 86400 && 60 % step > 0)
{
input_options.step = parseInt(step);
}
const scheme = $input.data('scheme') || common_options.scheme;
if (scheme)
{
input_options.scheme = scheme;
}
input_options.unitText = getUnitText(input_options);
$input.data('options', input_options);
if ($input.data('timepicker'))
{
// If timepicker is already initialized, then return
return this;
}
$input.data('timepicker', true).addClass('timepicker');
// Set inputmode
if (this.type == 'text' && !this.inputMode)
{
this.inputMode = 'tel';
}
const $label = jQuery('label[for="' + input_id + '"]');
const placement = (window.screen.width > 575) ? 'bottom' : 'top';
$input.on('change', function () {
this.value = this.value.replace(/^\s+|\s+$/g, '');
const options = $input.data('options');
const newTime = parseTime(this.value, options);
this.value = (newTime !== false) ? newTime.format(options.format) : '';
}).on('shown.bs.popover', function () {
if (window.screen.width > 575)
{
jQuery('#' + input_id + '-picker-content').find('.hour-input').select();
}
}).on('inserted.bs.popover', function () {
jQuery('.popover').find('[data-dismiss="popover"]').on('click', function () {
$input.popover('hide');
});
updatePicker($input, input_options);
}).on('hide.bs.popover', function () {
$input.data('view', null);
}).popover({
html: true,
placement: placement,
sanitize: false,
title: '<button class="close mt-n1" data-dismiss="popover">&times;</button>' + (($label.length > 0) ? $label.html() : 'Time'),
template: '<div id="' + input_id + '-picker-popover" class="popover timepicker-popover bs-popover-"' + placement + '" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div id="' + input_id + '-popover-body" class="popover-body border-bottom"></div><div class="popover-footer bg-light text-right px-3 py-2 rounded-lg" hidden="hidden"><button type="button" class="btn btn-secondary btn-sm" title="Close the picker" data-dismiss="popover"><i class="fas fa-times"></i> Close</button></div></div>',
trigger: (($toggles.length > 0) ? 'manual' : 'click'),
popperConfig: {
/*
modifiers: {
hide: {
enabled: false
},
preventOverflow: {
enabled: false,
// boundariesElement: 'window',
escapeWithReference: true
}
},
// positionFixed: true
*/
},
content: function () {
const options = $input.data('options');
const viewTime = parseTime($input.val() || options.defaultTime || '', options);
$input.data('viewtime', viewTime);
return '<div id="' + input_id + '-picker-content" class="timepicker-content" data-view="hour"></div>';
}
});
$toggles.on('click', function () {
$input.popover('toggle');
this.blur();
});
});
};
/**
* Default options
*
* @type {object}
* @todo add support for additional options
*/
jQuery.fn.timepicker.defaults = {
defaultTime: null,
format: 'hh:mm A',
maxTime: null,
minTime: null,
step: 60,
scheme: 'light'
};
/*
* Initialize timepickers
*/
document.addEventListener('DOMContentLoaded', function() {
jQuery('[data-toggle="timepicker"][data-target]').each(function () {
jQuery(jQuery(this).data('target')).timepicker();
});
});
//-------------------------------------
}());