UNPKG

samagra-x-enketo-core

Version:

Extensible Enketo form engine

1,191 lines (1,041 loc) 44.4 kB
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">&nbsp;</td><td><a href="#" data-action="incrementMinute"><span class="${this.icons.up}"></span></a></td>${this.showSeconds ? `<td class="separator">&nbsp;</td><td><a href="#" data-action="incrementSecond"><span class="${this.icons.up}"></span></a></td>` : ''}${this.showMeridian ? `<td class="separator">&nbsp;</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">&nbsp;</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">&nbsp;</td><td><a href="#" data-action="decrementSecond"><span class="${this.icons.down}"></span></a></td>` : ''}${this.showMeridian ? `<td class="separator">&nbsp;</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 );