import $ from 'jquery';
import event from '../../js/event';
/*!
* Timepicker
*
* Forked from https://github.com/jdewit/bootstrap-timepicker:
*
* Copyright 2013 Joris de Wit and timepicker contributors
*
* Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
* Contributors https://github.com/enketo/timepicker-basic/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
( ( ( $, window, document ) => {
// TIMEPICKER PUBLIC CLASS DEFINITION
const Timepicker = function( element, options ) {
this.widget = '';
this.$element = $( element );
this.defaultTime = options.defaultTime;
this.disableFocus = options.disableFocus;
this.disableMousewheel = options.disableMousewheel;
this.isOpen = options.isOpen;
this.minuteStep = options.minuteStep;
this.orientation = options.orientation;
this.secondStep = options.secondStep;
this.snapToStep = options.snapToStep;
this.showInputs = options.showInputs;
this.showMeridian = options.showMeridian;
this.meridianNotation = options.meridianNotation;
this.showSeconds = options.showSeconds;
this.template = options.template;
this.appendWidgetTo = options.appendWidgetTo;
this.showWidgetOnAddonClick = options.showWidgetOnAddonClick;
this.icons = options.icons;
this.maxHours = options.maxHours;
this.explicitMode = options.explicitMode; // If true 123 = 1:23, 12345 = 1:23:45, else invalid.
this.handleDocumentClick = e => {
const self = e.data.scope;
// This condition was inspired by datepicker.
// The element the timepicker is invoked on is the input but it has a sibling for addon/button.
if ( !( self.$element.parent().find( e.target ).length ||
self.$widget.is( e.target ) ||
self.$widget.find( e.target ).length ) ) {
self.hideWidget();
}
};
this._init();
};
Timepicker.prototype = {
constructor: Timepicker,
_init() {
const self = this;
if ( this.showWidgetOnAddonClick && ( this.$element.parent().hasClass( 'input-group' ) && this.$element.parent().hasClass( 'timepicker' ) ) ) {
this.$element.parent( '.input-group.timepicker' ).find( '.input-group-addon' ).on( {
'click.timepicker': $.proxy( this.showWidget, this )
} );
this.$element.on( {
'focus.timepicker': $.proxy( this.highlightUnit, this ),
'click.timepicker': $.proxy( this.highlightUnit, this ),
'keydown.timepicker': $.proxy( this.elementKeydown, this ),
'blur.timepicker': $.proxy( this.blurElement, this ),
'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy( this.mousewheel, this )
} );
} else {
if ( this.template ) {
this.$element.on( {
'focus.timepicker': $.proxy( this.showWidget, this ),
'click.timepicker': $.proxy( this.showWidget, this ),
'blur.timepicker': $.proxy( this.blurElement, this ),
'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy( this.mousewheel, this )
} );
} else {
this.$element.on( {
'focus.timepicker': $.proxy( this.highlightUnit, this ),
'click.timepicker': $.proxy( this.highlightUnit, this ),
'keydown.timepicker': $.proxy( this.elementKeydown, this ),
'blur.timepicker': $.proxy( this.blurElement, this ),
'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy( this.mousewheel, this )
} );
}
}
if ( this.template !== false ) {
this.$widget = $( this.getTemplate() ).on( 'click', $.proxy( this.widgetClick, this ) );
} else {
this.$widget = false;
}
if ( this.showInputs && this.$widget !== false ) {
this.$widget.find( 'input' ).each( function() {
$( this ).on( {
'click.timepicker': function() {
$( this ).select();
},
'keydown.timepicker': $.proxy( self.widgetKeydown, self ),
'keyup.timepicker': $.proxy( self.widgetKeyup, self )
} );
} );
}
this.setDefaultTime( this.defaultTime );
},
blurElement() {
this.highlightedUnit = null;
this.updateFromElementVal();
},
clear() {
this.hour = '';
this.minute = '';
this.second = '';
this.meridian = '';
this.$element.val( '' );
},
decrementHour() {
// If value is empty, make sure that first shown value is current hour.
if ( this.hour === '' ) {
this.hour = this.getCurrentHour();
this.incrementHour();
}
if ( this.showMeridian ) {
if ( this.hour === 1 ) {
this.hour = 12;
} else if ( this.hour === 12 ) {
this.hour--;
return this.toggleMeridian();
} else if ( this.hour === 0 ) {
this.hour = 11;
return this.toggleMeridian();
} else {
this.hour--;
}
} else {
if ( this.hour <= 0 ) {
this.hour = this.maxHours - 1;
} else {
this.hour--;
}
}
},
decrementMinute( step ) {
let newVal;
// If value is empty, make sure that first shown value is current minutes.
if ( this.minute === '' ) {
this.minute = this.getCurrentMinute();
this.incrementMinute();
}
if ( step ) {
newVal = this.minute - step;
} else {
newVal = this.minute - this.minuteStep;
}
if ( newVal < 0 ) {
this.decrementHour();
this.minute = newVal + 60;
} else {
this.minute = newVal;
}
},
decrementSecond() {
const newVal = this.second - this.secondStep;
if ( newVal < 0 ) {
this.decrementMinute( true );
this.second = newVal + 60;
} else {
this.second = newVal;
}
},
elementKeydown( e ) {
switch ( e.which ) {
case 9: //tab
if ( e.shiftKey ) {
if ( this.highlightedUnit === 'hour' ) {
this.hideWidget();
break;
}
this.highlightPrevUnit();
} else if ( ( this.showMeridian && this.highlightedUnit === 'meridian' ) || ( this.showSeconds && this.highlightedUnit === 'second' ) || ( !this.showMeridian && !this.showSeconds && this.highlightedUnit === 'minute' ) ) {
this.hideWidget();
break;
} else {
this.highlightNextUnit();
}
e.preventDefault();
this.updateFromElementVal();
break;
case 27: // escape
this.updateFromElementVal();
break;
case 37: // left arrow
e.preventDefault();
this.highlightPrevUnit();
this.updateFromElementVal();
break;
case 38: // up arrow
e.preventDefault();
switch ( this.highlightedUnit ) {
case 'hour':
this.incrementHour();
this.highlightHour();
break;
case 'minute':
this.incrementMinute();
this.highlightMinute();
break;
case 'second':
this.incrementSecond();
this.highlightSecond();
break;
case 'meridian':
this.toggleMeridian();
this.highlightMeridian();
break;
}
this.update();
break;
case 39: // right arrow
e.preventDefault();
this.highlightNextUnit();
this.updateFromElementVal();
break;
case 40: // down arrow
e.preventDefault();
switch ( this.highlightedUnit ) {
case 'hour':
this.decrementHour();
this.highlightHour();
break;
case 'minute':
this.decrementMinute();
this.highlightMinute();
break;
case 'second':
this.decrementSecond();
this.highlightSecond();
break;
case 'meridian':
this.toggleMeridian();
this.highlightMeridian();
break;
}
this.update();
break;
}
},
getCursorPosition() {
const input = this.$element.get( 0 );
if ( 'selectionStart' in input ) { // Standard-compliant browsers
return input.selectionStart;
} else if ( document.selection ) { // IE fix
input.focus();
const sel = document.selection.createRange(),
selLen = document.selection.createRange().text.length;
sel.moveStart( 'character', -input.value.length );
return sel.text.length - selLen;
}
},
getMeridianLength() {
const el = document.createElement( 'span' );
el.textContent = this.meridianNotation.am;
el.style.position = 'absolute';
document.querySelector( 'body' ).appendChild( el );
const amLength = el.scrollWidth;
el.textContent = this.meridianNotation.pm;
const pmLength = el.scrollWidth;
el.remove();
return amLength > pmLength ? amLength : pmLength;
},
getTemplate() {
let template, hourTemplate, minuteTemplate, secondTemplate, meridianTemplate, templateContent;
if ( this.showInputs ) {
const width = this.getMeridianLength() > 26 ? `style="width: ${this.getMeridianLength() + 24}px"` : '';
hourTemplate = `<input type="text" class="timepicker-hour" ${width}/>`;
minuteTemplate = `<input type="text" class="timepicker-minute" ${width}/>`;
secondTemplate = `<input type="text" class="timepicker-second" ${width}/>`;
meridianTemplate = `<input type="text" class="timepicker-meridian"${width}/>`;
} else {
hourTemplate = '<span class="timepicker-hour"></span>';
minuteTemplate = '<span class="timepicker-minute"></span>';
secondTemplate = '<span class="timepicker-second"></span>';
meridianTemplate = '<span class="timepicker-meridian"></span>';
}
templateContent = `<table><tr><td><a href="#" data-action="incrementHour"><span class="${this.icons.up}"></span></a></td><td class="separator"> </td><td><a href="#" data-action="incrementMinute"><span class="${this.icons.up}"></span></a></td>${this.showSeconds ?
`<td class="separator"> </td><td><a href="#" data-action="incrementSecond"><span class="${this.icons.up}"></span></a></td>` : ''}${this.showMeridian ?
`<td class="separator"> </td><td class="meridian-column"><a href="#" data-action="toggleMeridian"><span class="${this.icons.up}"></span></a></td>` : ''}</tr><tr><td>${hourTemplate}</td> <td class="separator">:</td><td>${minuteTemplate}</td> ${this.showSeconds ?
`<td class="separator">:</td><td>${secondTemplate}</td>` : ''}${this.showMeridian ?
`<td class="separator"> </td><td>${meridianTemplate}</td>` : ''}</tr><tr><td><a href="#" data-action="decrementHour"><span class="${this.icons.down}"></span></a></td><td class="separator"></td><td><a href="#" data-action="decrementMinute"><span class="${this.icons.down}"></span></a></td>${this.showSeconds ?
`<td class="separator"> </td><td><a href="#" data-action="decrementSecond"><span class="${this.icons.down}"></span></a></td>` : ''}${this.showMeridian ?
`<td class="separator"> </td><td><a href="#" data-action="toggleMeridian"><span class="${this.icons.down}"></span></a></td>` : ''}</tr></table>`;
switch ( this.template ) {
case 'dropdown':
template = `<div class="timepicker-widget dropdown-menu">${templateContent}</div>`;
break;
}
return template;
},
getTime() {
if ( this.hour === '' ) {
return '';
}
//return this.hour + ':' + ( this.minute.toString().length === 1 ? '0' + this.minute : this.minute ) + ( this.showSeconds ? ':' + ( this.second.toString().length === 1 ? '0' + this.second : this.second ) : '' ) + ( this.showMeridian ? ' ' + this.meridian : '' );
return `${this.hour.toString().length === 1 ? `0${this.hour}` : this.hour}:${this.minute.toString().length === 1 ? `0${this.minute}` : this.minute}${this.showSeconds ? `:${this.second.toString().length === 1 ? `0${this.second}` : this.second}` : ''}${this.showMeridian ? ` ${this.meridian}` : ''}`;
},
hideWidget() {
if ( this.isOpen === false ) {
return;
}
this.$element.trigger( {
'type': 'hide.timepicker',
'time': {
'value': this.getTime(),
'hours': this.hour,
'minutes': this.minute,
'seconds': this.second,
'meridian': this.meridian
}
} );
this.$widget.removeClass( 'open' );
$( document ).off( 'mousedown.timepicker, touchend.timepicker', this.handleDocumentClick );
this.isOpen = false;
// show/hide approach taken by datepicker
this.$widget.detach();
},
highlightUnit() {
this.position = this.getCursorPosition();
if ( this.position >= 0 && this.position <= 2 ) {
this.highlightHour();
} else if ( this.position >= 3 && this.position <= 5 ) {
this.highlightMinute();
} else if ( this.position >= 6 && this.position <= 8 ) {
if ( this.showSeconds ) {
this.highlightSecond();
} else {
this.highlightMeridian();
}
} else if ( this.position >= 9 && this.position <= 11 ) {
this.highlightMeridian();
}
},
highlightNextUnit() {
switch ( this.highlightedUnit ) {
case 'hour':
this.highlightMinute();
break;
case 'minute':
if ( this.showSeconds ) {
this.highlightSecond();
} else if ( this.showMeridian ) {
this.highlightMeridian();
} else {
this.highlightHour();
}
break;
case 'second':
if ( this.showMeridian ) {
this.highlightMeridian();
} else {
this.highlightHour();
}
break;
case 'meridian':
this.highlightHour();
break;
}
},
highlightPrevUnit() {
switch ( this.highlightedUnit ) {
case 'hour':
if ( this.showMeridian ) {
this.highlightMeridian();
} else if ( this.showSeconds ) {
this.highlightSecond();
} else {
this.highlightMinute();
}
break;
case 'minute':
this.highlightHour();
break;
case 'second':
this.highlightMinute();
break;
case 'meridian':
if ( this.showSeconds ) {
this.highlightSecond();
} else {
this.highlightMinute();
}
break;
}
},
highlightHour() {
const $element = this.$element.get( 0 ),
self = this;
this.highlightedUnit = 'hour';
if ( $element.setSelectionRange ) {
setTimeout( () => {
if ( self.hour < 10 ) {
$element.setSelectionRange( 0, 1 );
} else {
$element.setSelectionRange( 0, 2 );
}
}, 0 );
}
},
highlightMinute() {
const $element = this.$element.get( 0 ),
self = this;
this.highlightedUnit = 'minute';
if ( $element.setSelectionRange ) {
setTimeout( () => {
if ( self.hour < 10 ) {
$element.setSelectionRange( 2, 4 );
} else {
$element.setSelectionRange( 3, 5 );
}
}, 0 );
}
},
highlightSecond() {
const $element = this.$element.get( 0 ),
self = this;
this.highlightedUnit = 'second';
if ( $element.setSelectionRange ) {
setTimeout( () => {
if ( self.hour < 10 ) {
$element.setSelectionRange( 5, 7 );
} else {
$element.setSelectionRange( 6, 8 );
}
}, 0 );
}
},
highlightMeridian() {
const $element = this.$element.get( 0 ),
self = this;
this.highlightedUnit = 'meridian';
if ( $element.setSelectionRange ) {
if ( this.showSeconds ) {
setTimeout( () => {
if ( self.hour < 10 ) {
$element.setSelectionRange( 8, 10 );
} else {
$element.setSelectionRange( 9, 11 );
}
}, 0 );
} else {
setTimeout( () => {
if ( self.hour < 10 ) {
$element.setSelectionRange( 5, 7 );
} else {
$element.setSelectionRange( 6, 8 );
}
}, 0 );
}
}
},
getCurrentHour() {
const h24 = new Date().getHours();
return ( this.showMeridian ) ? h24 % 12 : h24;
},
getCurrentMinute() {
return new Date().getMinutes();
},
incrementHour() {
// If value is empty, make sure that first shown value is current hour.
if ( this.hour === '' ) {
this.hour = this.getCurrentHour();
this.decrementHour();
}
// if this.hour is empty string
if ( this.showMeridian ) {
if ( this.hour === 11 ) {
this.hour++;
return this.toggleMeridian();
} else if ( this.hour === 12 ) {
this.hour = 0;
}
}
if ( this.hour === this.maxHours - 1 ) {
this.hour = 0;
return;
}
this.hour++;
},
incrementMinute( step ) {
let newVal;
// If value is empty, make sure that first shown value is current minutes.
if ( this.minute === '' ) {
this.minute = this.getCurrentMinute();
this.decrementMinute();
}
if ( step ) {
newVal = this.minute + step;
} else {
newVal = this.minute + this.minuteStep - ( this.minute % this.minuteStep );
}
if ( newVal > 59 ) {
this.incrementHour();
this.minute = newVal - 60;
} else {
this.minute = newVal;
}
},
incrementSecond() {
const newVal = this.second + this.secondStep - ( this.second % this.secondStep );
if ( newVal > 59 ) {
this.incrementMinute( true );
this.second = newVal - 60;
} else {
this.second = newVal;
}
},
mousewheel( e ) {
if ( this.disableMousewheel ) {
return;
}
e.preventDefault();
e.stopPropagation();
const delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
let scrollTo = null;
if ( e.type === 'mousewheel' ) {
scrollTo = ( e.originalEvent.wheelDelta * -1 );
} else if ( e.type === 'DOMMouseScroll' ) {
scrollTo = 40 * e.originalEvent.detail;
}
if ( scrollTo ) {
e.preventDefault();
$( this ).scrollTop( scrollTo + $( this ).scrollTop() );
}
switch ( this.highlightedUnit ) {
case 'minute':
if ( delta > 0 ) {
this.incrementMinute();
} else {
this.decrementMinute();
}
this.highlightMinute();
break;
case 'second':
if ( delta > 0 ) {
this.incrementSecond();
} else {
this.decrementSecond();
}
this.highlightSecond();
break;
case 'meridian':
this.toggleMeridian();
this.highlightMeridian();
break;
default:
if ( delta > 0 ) {
this.incrementHour();
} else {
this.decrementHour();
}
this.highlightHour();
break;
}
return false;
},
/**
* Given a segment value like 43, will round and snap the segment
* to the nearest "step", like 45 if step is 15. Segment will
* "overflow" to 0 if it's larger than 59 or would otherwise
* round up to 60.
*
* @param {number} segment - The segment value
* @param {number} step - The step
*/
changeToNearestStep( segment, step ) {
if ( segment % step === 0 ) {
return segment;
}
if ( Math.round( ( segment % step ) / step ) ) {
return ( segment + ( step - segment % step ) ) % 60;
} else {
return segment - segment % step;
}
},
// This method was adapted from datepicker.
place() {
if ( this.isInline ) {
return;
}
const widgetWidth = this.$widget.outerWidth(),
widgetHeight = this.$widget.outerHeight(),
visualPadding = 10,
windowWidth =
$( window ).width(),
windowHeight = $( window ).height(),
scrollTop = $( window ).scrollTop();
const zIndex = parseInt( this.$element.parents().filter( function() {
return $( this ).css( 'z-index' ) !== 'auto';
} ).first().css( 'z-index' ), 10 ) + 10;
const offset = this.component ? this.component.parent().offset() : this.$element.offset();
const height = this.component ? this.component.outerHeight( true ) : this.$element.outerHeight( false );
const width = this.component ? this.component.outerWidth( true ) : this.$element.outerWidth( false );
let left = offset.left,
top = offset.top;
this.$widget.removeClass( 'timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left' );
if ( this.orientation.x !== 'auto' ) {
this.$widget.addClass( `timepicker-orient-${this.orientation.x}` );
if ( this.orientation.x === 'right' ) {
left -= widgetWidth - width;
}
} else {
// auto x orientation is best-placement: if it crosses a window edge, fudge it sideways
// Default to left
this.$widget.addClass( 'timepicker-orient-left' );
if ( offset.left < 0 ) {
left -= offset.left - visualPadding;
} else if ( offset.left + widgetWidth > windowWidth ) {
left = windowWidth - widgetWidth - visualPadding;
}
}
// auto y orientation is best-situation: top or bottom, no fudging, decision based on which shows more of the widget
let yorient = this.orientation.y,
topOverflow, bottomOverflow;
if ( yorient === 'auto' ) {
topOverflow = -scrollTop + offset.top - widgetHeight;
bottomOverflow = scrollTop + windowHeight - ( offset.top + height + widgetHeight );
if ( Math.max( topOverflow, bottomOverflow ) === bottomOverflow ) {
yorient = 'top';
} else {
yorient = 'bottom';
}
}
this.$widget.addClass( `timepicker-orient-${yorient}` );
if ( yorient === 'top' ) {
top += height;
} else {
top -= widgetHeight + parseInt( this.$widget.css( 'padding-top' ), 10 );
}
this.$widget.css( {
top,
left,
zIndex
} );
},
remove() {
$( 'document' ).off( '.timepicker' );
if ( this.$widget ) {
this.$widget.remove();
}
delete this.$element.data().timepicker;
},
setDefaultTime( defaultTime ) {
if ( !this.$element.val() ) {
if ( defaultTime === 'current' ) {
const dTime = new Date();
let hours = dTime.getHours();
let minutes = dTime.getMinutes();
let seconds = dTime.getSeconds();
let meridian = this.meridianNotation.am;
if ( seconds !== 0 ) {
seconds = Math.ceil( dTime.getSeconds() / this.secondStep ) * this.secondStep;
if ( seconds === 60 ) {
minutes += 1;
seconds = 0;
}
}
if ( minutes !== 0 ) {
minutes = Math.ceil( dTime.getMinutes() / this.minuteStep ) * this.minuteStep;
if ( minutes === 60 ) {
hours += 1;
minutes = 0;
}
}
if ( this.showMeridian ) {
if ( hours === 0 ) {
hours = 12;
} else if ( hours >= 12 ) {
if ( hours > 12 ) {
hours = hours - 12;
}
meridian = this.meridianNotation.pm;
} else {
meridian = this.meridianNotation.am;
}
}
this.hour = hours;
this.minute = minutes;
this.second = seconds;
this.meridian = meridian;
this.update();
} else if ( defaultTime === false ) {
this.hour = 0;
this.minute = 0;
this.second = 0;
this.meridian = this.meridianNotation.am;
} else {
this.setTime( defaultTime );
}
} else {
this.updateFromElementVal();
}
},
setTime( time, ignoreWidget ) {
if ( !time ) {
this.clear();
return;
}
let timeMode, timeArray, hour, minute, second, meridian;
if ( typeof time === 'object' && time.getMonth ) {
// this is a date object
hour = time.getHours();
minute = time.getMinutes();
second = time.getSeconds();
if ( this.showMeridian ) {
meridian = this.meridianNotation.am;
if ( hour > 12 ) {
meridian = this.meridianNotation.pm;
hour = hour % 12;
}
if ( hour === 12 ) {
meridian = this.meridianNotation.pm;
}
}
} else {
let am = this.showMeridian ? this.meridianNotation.am : 'am';
let pm = this.showMeridian ? this.meridianNotation.pm : 'pm';
timeMode = ( ( new RegExp( am, 'i' ) ).test( time ) ? 1 : 0 ) + ( ( new RegExp( pm, 'i' ) ).test( time ) ? 2 : 0 ); // 0 = none, 1 = AM, 2 = PM, 3 = BOTH.
if ( timeMode > 2 ) { // If both are present, fail.
this.clear();
return;
}
timeArray = time.replace( /[^0-9:]/g, '' ).split( ':' );
hour = timeArray[ 0 ] ? timeArray[ 0 ].toString() : timeArray.toString();
if ( this.explicitMode && hour.length > 2 && ( hour.length % 2 ) !== 0 ) {
this.clear();
return;
}
minute = timeArray[ 1 ] ? timeArray[ 1 ].toString() : '';
second = timeArray[ 2 ] ? timeArray[ 2 ].toString() : '';
// adaptive time parsing
if ( hour.length > 4 ) {
second = hour.slice( -2 );
hour = hour.slice( 0, -2 );
}
if ( hour.length > 2 ) {
minute = hour.slice( -2 );
hour = hour.slice( 0, -2 );
}
if ( minute.length > 2 ) {
second = minute.slice( -2 );
minute = minute.slice( 0, -2 );
}
hour = parseInt( hour, 10 );
minute = parseInt( minute, 10 );
second = parseInt( second, 10 );
if ( isNaN( hour ) ) {
hour = 0;
}
if ( isNaN( minute ) ) {
minute = 0;
}
if ( isNaN( second ) ) {
second = 0;
}
// Adjust the time based upon unit boundary.
// NOTE: Negatives will never occur due to time.replace() above.
if ( second > 59 ) {
second = 59;
}
if ( minute > 59 ) {
minute = 59;
}
if ( hour >= this.maxHours ) {
// No day/date handling.
hour = this.maxHours - 1;
}
if ( this.showMeridian ) {
if ( hour >= 12 ) {
// Force PM.
if ( !timeMode ) {
timeMode = 2;
}
hour -= 12;
}
if ( !timeMode ) {
timeMode = 1;
}
if ( hour === 0 ) {
hour = 12; // AM or PM, reset to 12. 0 AM = 12 AM. 0 PM = 12 PM, etc.
}
meridian = timeMode === 1 ? this.meridianNotation.am : this.meridianNotation.pm;
} else if ( hour < 12 && timeMode === 2 ) {
hour += 12;
} else {
if ( hour >= this.maxHours ) {
hour = this.maxHours - 1;
} else if ( ( hour < 0 ) || ( hour === 12 && timeMode === 1 ) ) {
hour = 0;
}
}
}
this.hour = hour;
if ( this.snapToStep ) {
this.minute = this.changeToNearestStep( minute, this.minuteStep );
this.second = this.changeToNearestStep( second, this.secondStep );
} else {
this.minute = minute;
this.second = second;
}
this.meridian = meridian;
this.update( ignoreWidget );
},
showWidget() {
if ( this.isOpen ) {
return;
}
if ( this.$element.is( ':disabled' ) ) {
return;
}
// make sure the widget is in sync with input
this.setTime( this.$element.val() );
this.updateWidget();
// show/hide approach taken by datepicker
this.$widget.appendTo( this.appendWidgetTo );
$( document ).on( 'mousedown.timepicker, touchend.timepicker', {
scope: this
}, this.handleDocumentClick );
this.$element.trigger( {
'type': 'show.timepicker',
'time': {
'value': this.getTime(),
'hours': this.hour,
'minutes': this.minute,
'seconds': this.second,
'meridian': this.meridian
}
} );
this.place();
if ( this.disableFocus ) {
this.$element.blur();
}
if ( this.hour === '' ) {
if ( this.defaultTime ) {
this.setDefaultTime( this.defaultTime );
}
}
if ( this.isOpen === false ) {
this.$widget.addClass( 'open' );
}
this.isOpen = true;
},
toggleMeridian() {
this.meridian = this.meridian === this.meridianNotation.am ? this.meridianNotation.pm : this.meridianNotation.am;
},
update( ignoreWidget ) {
this.updateElement();
if ( !ignoreWidget ) {
this.updateWidget();
}
this.$element.trigger( {
'type': 'changeTime.timepicker',
'time': {
'value': this.getTime(),
'hours': this.hour,
'minutes': this.minute,
'seconds': this.second,
'meridian': this.meridian
}
} );
},
updateElement() {
this.$element.val( this.getTime() );
this.$element[ 0 ].dispatchEvent( event.Change() );
},
updateFromElementVal() {
this.setTime( this.$element.val() );
},
updateWidget() {
if ( this.$widget === false ) {
return;
}
const hour = this.hour,
minute = this.minute.toString().length === 1 ? `0${this.minute}` : this.minute,
second = this.second.toString().length === 1 ? `0${this.second}` : this.second;
if ( this.showInputs ) {
this.$widget.find( 'input.timepicker-hour' ).val( hour );
this.$widget.find( 'input.timepicker-minute' ).val( minute );
if ( this.showSeconds ) {
this.$widget.find( 'input.timepicker-second' ).val( second );
}
if ( this.showMeridian ) {
this.$widget.find( 'input.timepicker-meridian' ).val( this.meridian );
}
} else {
this.$widget.find( 'span.timepicker-hour' ).text( hour );
this.$widget.find( 'span.timepicker-minute' ).text( minute );
if ( this.showSeconds ) {
this.$widget.find( 'span.timepicker-second' ).text( second );
}
if ( this.showMeridian ) {
this.$widget.find( 'span.timepicker-meridian' ).text( this.meridian );
}
}
},
updateFromWidgetInputs() {
if ( this.$widget === false ) {
return;
}
const t = `${this.$widget.find( 'input.timepicker-hour' ).val()}:${this.$widget.find( 'input.timepicker-minute' ).val()}${this.showSeconds ? `:${this.$widget.find( 'input.timepicker-second' ).val()}` : ''}${this.showMeridian ? this.$widget.find( 'input.timepicker-meridian' ).val() : ''}`;
this.setTime( t, true );
},
widgetClick( e ) {
e.stopPropagation();
e.preventDefault();
const $input = $( e.target ),
action = $input.closest( 'a' ).data( 'action' );
if ( action ) {
this[ action ]();
}
this.update();
if ( $input.is( 'input' ) ) {
$input.get( 0 ).setSelectionRange( 0, 2 );
}
},
widgetKeydown( e ) {
const $input = $( e.target ),
name = $input.attr( 'class' ).replace( 'timepicker-', '' );
switch ( e.which ) {
case 9: //tab
if ( e.shiftKey ) {
if ( name === 'hour' ) {
return this.hideWidget();
}
} else if ( ( this.showMeridian && name === 'meridian' ) || ( this.showSeconds && name === 'second' ) || ( !this.showMeridian && !this.showSeconds && name === 'minute' ) ) {
return this.hideWidget();
}
break;
case 27: // escape
this.hideWidget();
break;
case 38: // up arrow
e.preventDefault();
switch ( name ) {
case 'hour':
this.incrementHour();
break;
case 'minute':
this.incrementMinute();
break;
case 'second':
this.incrementSecond();
break;
case 'meridian':
this.toggleMeridian();
break;
}
this.setTime( this.getTime() );
$input.get( 0 ).setSelectionRange( 0, 2 );
break;
case 40: // down arrow
e.preventDefault();
switch ( name ) {
case 'hour':
this.decrementHour();
break;
case 'minute':
this.decrementMinute();
break;
case 'second':
this.decrementSecond();
break;
case 'meridian':
this.toggleMeridian();
break;
}
this.setTime( this.getTime() );
$input.get( 0 ).setSelectionRange( 0, 2 );
break;
}
},
widgetKeyup( e ) {
if ( ( e.which === 65 ) || ( e.which === 77 ) || ( e.which === 80 ) || ( e.which === 46 ) || ( e.which === 8 ) || ( e.which >= 48 && e.which <= 57 ) || ( e.which >= 96 && e.which <= 105 ) ) {
this.updateFromWidgetInputs();
}
}
};
//TIMEPICKER PLUGIN DEFINITION
$.fn.timepicker = function( option ) {
const args = Array( ...arguments );
args.shift();
return this.each( function() {
const $this = $( this );
let data = $this.data( 'timepicker' );
const options = typeof option === 'object' && option;
if ( !data ) {
$this.data( 'timepicker', ( data = new Timepicker( this, $.extend( {}, $.fn.timepicker.defaults, options, $( this ).data() ) ) ) );
}
if ( typeof option === 'string' ) {
data[ option ]( ...args );
}
} );
};
$.fn.timepicker.defaults = {
defaultTime: 'current',
disableFocus: false,
disableMousewheel: false,
isOpen: false,
minuteStep: 15,
orientation: {
x: 'auto',
y: 'auto'
},
secondStep: 15,
snapToStep: false,
showSeconds: false,
showInputs: true,
showMeridian: true,
meridianNotation: {
am: 'AM',
pm: 'PM'
},
template: 'dropdown',
appendWidgetTo: 'body',
showWidgetOnAddonClick: true,
icons: {
up: 'glyphicon glyphicon-chevron-up',
down: 'glyphicon glyphicon-chevron-down'
},
maxHours: 24,
explicitMode: false
};
$.fn.timepicker.Constructor = Timepicker;
$( document ).on(
'focus.timepicker.data-api click.timepicker.data-api',
'[data-provide="timepicker"]',
function( e ) {
const $this = $( this );
if ( $this.data( 'timepicker' ) ) {
return;
}
e.preventDefault();
// component click requires us to explicitly show it
$this.timepicker();
}
);
} ) )( $, window, document );