fm-timepicker
Advanced tools
Comparing version 4.0.0 to 4.0.1
@@ -34,712 +34,673 @@ /** | ||
// Declare fmComponents module if it doesn't exist. | ||
try { | ||
angular.module( "fm.components" ); | ||
} catch( ignored ) { | ||
angular.module( "fm.components", [] ); | ||
} | ||
angular.module( "fmTimepicker", [] ); | ||
angular.module( "fm.components" ) | ||
.filter( "fmTimeFormat", function() { | ||
return function( input, format ) { | ||
if( typeof input === "number" ) { | ||
input = moment( input ); | ||
} | ||
return moment( input ).format( format ); | ||
}; | ||
} ) | ||
angular.module( "fmTimepicker" ) | ||
.filter( "fmTimeFormat", fmTimeFormat ) | ||
.filter( "fmTimeInterval", fmTimeInterval ) | ||
.controller( "fmTimepickerController", fmTimepickerController ) | ||
.directive( "fmTimepickerToggle", fmTimepickerToggle ) | ||
.directive( "fmTimepicker", fmTimepicker ); | ||
.filter( "fmTimeInterval", function() { | ||
return function( input, start, end, interval ) { | ||
if( !start || !end ) { | ||
return input; | ||
} | ||
function fmTimeFormat() { | ||
return function fmTimeFormatFilter( input, format ) { | ||
if( typeof input === "number" ) { | ||
input = moment( input ); | ||
} | ||
return moment( input ).format( format ); | ||
}; | ||
} | ||
start = moment( start ); | ||
end = moment( end ); | ||
interval = interval || moment.duration( 30, "minutes" ); | ||
for( var time = start.clone(); +time <= +end; time.add( interval ) ) { | ||
// We're using the UNIX offset integer value here. | ||
// When trying to return the actual moment instance (and then later format it through a filter), | ||
// you will get an infinite digest loop, because the returned objects in the resulting array | ||
// will always be new, unique instances. We always need to return the identical, literal values for each input. | ||
input.push( +time ); | ||
} | ||
function fmTimeInterval() { | ||
return function fmTimeIntervalFilter( input, start, end, interval ) { | ||
if( !start || !end ) { | ||
return input; | ||
}; | ||
} ) | ||
} | ||
.controller( "fmTimepickerController", [ "$scope", function( $scope ) { | ||
start = moment( start ); | ||
end = moment( end ); | ||
interval = interval || moment.duration( 30, "minutes" ); | ||
// Create day of reference | ||
$scope.reference = $scope.reference ? moment( $scope.reference ) : moment(); | ||
for( var time = start.clone(); +time <= +end; time.add( interval ) ) { | ||
// We're using the UNIX offset integer value here. | ||
// When trying to return the actual moment instance (and then later format it through a filter), | ||
// you will get an infinite digest loop, because the returned objects in the resulting array | ||
// will always be new, unique instances. We always need to return the identical, literal values for each input. | ||
input.push( +time ); | ||
} | ||
return input; | ||
}; | ||
} | ||
$scope.style = $scope.style || "dropdown"; | ||
$scope.isOpen = $scope.isOpen || false; | ||
$scope.format = $scope.format || "LT"; | ||
$scope.startTime = $scope.startTime || moment( $scope.reference ).startOf( "day" ); | ||
$scope.endTime = $scope.endTime || moment( $scope.reference ).endOf( "day" ); | ||
$scope.interval = $scope.interval || moment.duration( 30, "minutes" ); | ||
$scope.largeInterval = $scope.largeInterval || moment.duration( 60, "minutes" ); | ||
$scope.strict = $scope.strict || false; | ||
$scope.btnClass = $scope.btnClass || "btn-default"; | ||
/* @ngInject */ | ||
function fmTimepickerController( $scope ) { | ||
if( moment.tz ) { | ||
$scope.startTime.tz( $scope.reference.tz() ); | ||
$scope.endTime.tz( $scope.reference.tz() ); | ||
} | ||
// Create day of reference | ||
$scope.fmReference = $scope.fmReference ? moment( $scope.fmReference ) : moment(); | ||
if( $scope.strict ) { | ||
// Round the model value up to the next valid time that fits the configured interval. | ||
var modelMilliseconds = $scope.ngModel.valueOf(); | ||
var intervalMilliseconds = $scope.interval.asMilliseconds(); | ||
$scope.fmStyle = $scope.fmStyle || "dropdown"; | ||
$scope.fmIsOpen = $scope.fmIsOpen || false; | ||
$scope.fmFormat = $scope.fmFormat || "LT"; | ||
$scope.fmStartTime = $scope.fmStartTime || moment( $scope.fmReference ).startOf( "day" ); | ||
$scope.fmEndTime = $scope.fmEndTime || moment( $scope.fmReference ).endOf( "day" ); | ||
$scope.fmInterval = $scope.fmInterval || moment.duration( 30, "minutes" ); | ||
$scope.fmLargeInterval = $scope.fmLargeInterval || moment.duration( 60, "minutes" ); | ||
$scope.fmStrict = $scope.fmStrict || false; | ||
$scope.fmBtnClass = $scope.fmBtnClass || "btn-default"; | ||
modelMilliseconds -= modelMilliseconds % intervalMilliseconds; | ||
modelMilliseconds += intervalMilliseconds; | ||
if( moment.tz ) { | ||
$scope.fmStartTime.tz( $scope.fmReference.tz() ); | ||
$scope.fmEndTime.tz( $scope.fmReference.tz() ); | ||
} | ||
$scope.ngModel = moment( modelMilliseconds ); | ||
} | ||
if( $scope.fmStrict ) { | ||
// Round the model value up to the next valid time that fits the configured interval. | ||
var modelMilliseconds = $scope.ngModel.valueOf(); | ||
var intervalMilliseconds = $scope.fmInterval.asMilliseconds(); | ||
/** | ||
* Makes sure that the moment instances we work with all use the same day as reference. | ||
* We need this because we might construct moment instances from all kinds of sources, | ||
* in the time picker, we only care about time values though and we still want to compare | ||
* them through the moment mechanics (which respect the full date). | ||
* @param {Moment} [day] If day is given, it will be constrained to the reference day, otherwise all members will be constrained. | ||
* @return {Moment} If day was provided as parameter, it will be returned as well. | ||
*/ | ||
$scope.constrainToReference = function( day ) { | ||
if( day ) { | ||
if( moment.tz ) { | ||
day.tz( $scope.reference.tz() ); | ||
} | ||
modelMilliseconds -= modelMilliseconds % intervalMilliseconds; | ||
modelMilliseconds += intervalMilliseconds; | ||
if( !day.isSame( $scope.reference, "day" ) ) { | ||
day.year( $scope.reference.year() ).month( $scope.reference.month() ).date( $scope.reference.date() ); | ||
} | ||
return day; | ||
$scope.ngModel = moment( modelMilliseconds ); | ||
} | ||
} else { | ||
if( !$scope.startTime.isSame( $scope.reference, "day" ) ) { | ||
$scope.startTime.year( $scope.reference.year() ).month( $scope.reference.month() ).date( $scope.reference.date() ); | ||
} | ||
if( !$scope.endTime.isSame( $scope.reference, "day" ) ) { | ||
$scope.endTime.year( $scope.reference.year() ).month( $scope.reference.month() ).date( $scope.reference.date() ); | ||
} | ||
if( $scope.ngModel && !$scope.ngModel.isSame( $scope.reference, "day" ) ) { | ||
$scope.ngModel.year( $scope.reference.year() ).month( $scope.reference.month() ).date( $scope.reference.date() ); | ||
} | ||
/** | ||
* Makes sure that the moment instances we work with all use the same day as fmReference. | ||
* We need this because we might construct moment instances from all kinds of sources, | ||
* in the time picker, we only care about time values though and we still want to compare | ||
* them through the moment mechanics (which respect the full date). | ||
* @param {Moment} [day] If day is given, it will be constrained to the fmReference day, otherwise all members will be constrained. | ||
* @return {Moment} If day was provided as parameter, it will be returned as well. | ||
*/ | ||
$scope.constrainToReference = function( day ) { | ||
if( day ) { | ||
if( moment.tz ) { | ||
day.tz( $scope.fmReference.tz() ); | ||
} | ||
return null; | ||
}; | ||
$scope.constrainToReference(); | ||
/** | ||
* Returns a time value that is within the bounds given by the start and end time parameters. | ||
* @param {Moment} time The time value that should be constrained to be within the given bounds. | ||
* @returns {Moment} A new time value within the bounds, or the input instance. | ||
*/ | ||
$scope.ensureTimeIsWithinBounds = function( time ) { | ||
// We expect "time" to be a Moment instance; otherwise bail. | ||
if( !time || !moment.isMoment( time ) ) { | ||
return time; | ||
if( !day.isSame( $scope.fmReference, "day" ) ) { | ||
day.year( $scope.fmReference.year() ).month( $scope.fmReference.month() ).date( $scope.fmReference.date() ); | ||
} | ||
// Constrain model value to be in given bounds. | ||
if( time.isBefore( $scope.startTime ) ) { | ||
return moment( $scope.startTime ); | ||
return day; | ||
} else { | ||
if( !$scope.fmStartTime.isSame( $scope.fmReference, "day" ) ) { | ||
$scope.fmStartTime.year( $scope.fmReference.year() ).month( $scope.fmReference.month() ).date( | ||
$scope.fmReference.date() ); | ||
} | ||
if( time.isAfter( $scope.endTime ) ) { | ||
return moment( $scope.endTime ); | ||
if( !$scope.fmEndTime.isSame( $scope.fmReference, "day" ) ) { | ||
$scope.fmEndTime.year( $scope.fmReference.year() ).month( $scope.fmReference.month() ).date( $scope.fmReference.date() ); | ||
} | ||
if( $scope.ngModel && !$scope.ngModel.isSame( $scope.fmReference, "day" ) ) { | ||
$scope.ngModel.year( $scope.fmReference.year() ).month( $scope.fmReference.month() ).date( $scope.fmReference.date() ); | ||
} | ||
} | ||
return null; | ||
}; | ||
$scope.constrainToReference(); | ||
/** | ||
* Returns a time value that is within the bounds given by the start and end time parameters. | ||
* @param {Moment} time The time value that should be constrained to be within the given bounds. | ||
* @returns {Moment} A new time value within the bounds, or the input instance. | ||
*/ | ||
$scope.ensureTimeIsWithinBounds = function( time ) { | ||
// We expect "time" to be a Moment instance; otherwise bail. | ||
if( !time || !moment.isMoment( time ) ) { | ||
return time; | ||
}; | ||
$scope.ngModel = $scope.ensureTimeIsWithinBounds( $scope.ngModel ); | ||
} | ||
// Constrain model value to be in given bounds. | ||
if( time.isBefore( $scope.fmStartTime ) ) { | ||
return moment( $scope.fmStartTime ); | ||
} | ||
if( time.isAfter( $scope.fmEndTime ) ) { | ||
return moment( $scope.fmEndTime ); | ||
} | ||
return time; | ||
}; | ||
$scope.ngModel = $scope.ensureTimeIsWithinBounds( $scope.ngModel ); | ||
/** | ||
* Utility method to find the index of an item, in our collection of possible values, that matches a given time value. | ||
* @param {Moment} model A moment instance to look for in our possible values. | ||
*/ | ||
$scope.findActiveIndex = function( model ) { | ||
$scope.activeIndex = 0; | ||
if( !model ) { | ||
return; | ||
/** | ||
* Utility method to find the index of an item, in our collection of possible values, that matches a given time value. | ||
* @param {Moment} model A moment instance to look for in our possible values. | ||
*/ | ||
$scope.findActiveIndex = function( model ) { | ||
$scope.activeIndex = 0; | ||
if( !model ) { | ||
return; | ||
} | ||
// We step through each possible value instead of calculating the index directly, | ||
// to make sure we account for DST changes in the reference day. | ||
for( var time = $scope.fmStartTime.clone(); +time <= +$scope.fmEndTime; time.add( $scope.fmInterval ), ++$scope.activeIndex ) { | ||
if( time.isSame( model ) ) { | ||
break; | ||
} | ||
// We step through each possible value instead of calculating the index directly, | ||
// to make sure we account for DST changes in the reference day. | ||
for( var time = $scope.startTime.clone(); +time <= +$scope.endTime; time.add( $scope.interval ), ++$scope.activeIndex ) { | ||
if( time.isSame( model ) ) { | ||
break; | ||
// Check if we've already passed the time value that would fit our current model. | ||
if( time.isAfter( model ) ) { | ||
// If we're in strict mode, set an invalid index. | ||
if( $scope.fmStrict ) { | ||
$scope.activeIndex = -1; | ||
} | ||
// Check if we've already passed the time value that would fit our current model. | ||
if( time.isAfter( model ) ) { | ||
// If we're in strict mode, set an invalid index. | ||
if( $scope.strict ) { | ||
$scope.activeIndex = -1; | ||
} | ||
// If we're not in strict mode, decrease the index to select the previous item (the one we just passed). | ||
$scope.activeIndex -= 1; | ||
// Now bail out and use whatever index we determined. | ||
break; | ||
} | ||
// If we're not in strict mode, decrease the index to select the previous item (the one we just passed). | ||
$scope.activeIndex -= 1; | ||
// Now bail out and use whatever index we determined. | ||
break; | ||
} | ||
}; | ||
// The index of the last element in our time value collection. | ||
$scope.largestPossibleIndex = Number.MAX_VALUE; | ||
// The amount of list items we should skip when we perform a large jump through the collection. | ||
$scope.largeIntervalIndexJump = Number.MAX_VALUE; | ||
// Seed the active index based on the current model value. | ||
$scope.findActiveIndex( $scope.ngModel ); | ||
} | ||
}; | ||
// The index of the last element in our time value collection. | ||
$scope.largestPossibleIndex = Number.MAX_VALUE; | ||
// The amount of list items we should skip when we perform a large jump through the collection. | ||
$scope.largeIntervalIndexJump = Number.MAX_VALUE; | ||
// Seed the active index based on the current model value. | ||
$scope.findActiveIndex( $scope.ngModel ); | ||
// Check the supplied interval for validity. | ||
$scope.$watch( "interval", function( newInterval, oldInterval ) { | ||
if( newInterval.asMilliseconds() < 1 ) { | ||
console.error( | ||
"[fm-timepicker] Error: Supplied interval length is smaller than 1ms! Reverting to default." ); | ||
$scope.interval = moment.duration( 30, "minutes" ); | ||
} | ||
} ); | ||
// Check the supplied large interval for validity. | ||
$scope.$watch( "largeInterval", function( newInterval, oldInterval ) { | ||
if( newInterval.asMilliseconds() < 10 ) { | ||
console.error( | ||
"[fm-timepicker] Error: Supplied large interval length is smaller than 10ms! Reverting to default." ); | ||
$scope.largeInterval = moment.duration( 60, "minutes" ); | ||
} | ||
} ); | ||
// Watch the given interval values. | ||
$scope.$watchCollection( "[interval,largeInterval]", function( newValues ) { | ||
// Pick array apart. | ||
var newInterval = newValues[ 0 ]; | ||
var newLargeInterval = newValues[ 1 ]; | ||
// Get millisecond values for the intervals. | ||
var newIntervalMilliseconds = newInterval.asMilliseconds(); | ||
var newLargeIntervalMilliseconds = newLargeInterval.asMilliseconds(); | ||
// Check if the large interval is a multiple of the interval. | ||
if( 0 !== ( newLargeIntervalMilliseconds % newIntervalMilliseconds ) ) { | ||
console.warn( | ||
"[fm-timepicker] Warning: Large interval is not a multiple of interval! Using internally computed value instead." ); | ||
$scope.largeInterval = moment.duration( newIntervalMilliseconds * 5 ); | ||
newLargeIntervalMilliseconds = $scope.largeInterval.asMilliseconds(); | ||
} | ||
// Calculate how many indices we need to skip for a large jump through our collection. | ||
$scope.largeIntervalIndexJump = newLargeIntervalMilliseconds / newIntervalMilliseconds; | ||
} ); | ||
} ] ) | ||
// Check the supplied interval for validity. | ||
$scope.$watch( "fmInterval", function intervalWatcher( newInterval, oldInterval ) { | ||
if( newInterval.asMilliseconds() < 1 ) { | ||
console.error( | ||
"[fm-timepicker] Error: Supplied interval length is smaller than 1ms! Reverting to default." ); | ||
$scope.fmInterval = moment.duration( 30, "minutes" ); | ||
} | ||
} ); | ||
// Check the supplied large interval for validity. | ||
$scope.$watch( "fmLargeInterval", function largeIntervalWatcher( newInterval, oldInterval ) { | ||
if( newInterval.asMilliseconds() < 10 ) { | ||
console.error( | ||
"[fm-timepicker] Error: Supplied large interval length is smaller than 10ms! Reverting to default." ); | ||
$scope.fmLargeInterval = moment.duration( 60, "minutes" ); | ||
} | ||
} ); | ||
// Watch the given interval values. | ||
$scope.$watchCollection( "[fmInterval,fmLargeInterval]", function intervalsWatcher( newValues ) { | ||
// Pick array apart. | ||
var newInterval = newValues[ 0 ]; | ||
var newLargeInterval = newValues[ 1 ]; | ||
// Get millisecond values for the intervals. | ||
var newIntervalMilliseconds = newInterval.asMilliseconds(); | ||
var newLargeIntervalMilliseconds = newLargeInterval.asMilliseconds(); | ||
// Check if the large interval is a multiple of the interval. | ||
if( 0 !== ( newLargeIntervalMilliseconds % newIntervalMilliseconds ) ) { | ||
console.warn( | ||
"[fm-timepicker] Warning: Large interval is not a multiple of interval! Using internally computed value instead." ); | ||
$scope.fmLargeInterval = moment.duration( newIntervalMilliseconds * 5 ); | ||
newLargeIntervalMilliseconds = $scope.fmLargeInterval.asMilliseconds(); | ||
} | ||
// Calculate how many indices we need to skip for a large jump through our collection. | ||
$scope.largeIntervalIndexJump = newLargeIntervalMilliseconds / newIntervalMilliseconds; | ||
} ); | ||
} | ||
fmTimepickerController.$inject = ["$scope"]; | ||
.directive( "fmTimepickerToggle", function() { | ||
return { | ||
restrict : "A", | ||
link : function postLink( scope, element, attributes ) { | ||
// Toggle the popup when the toggle button is clicked. | ||
element.bind( "click", function() { | ||
if( scope.isOpen ) { | ||
scope.focusInputElement(); | ||
scope.closePopup(); | ||
} else { | ||
// Focusing the input element will automatically open the popup | ||
scope.focusInputElement(); | ||
} | ||
} ); | ||
} | ||
}; | ||
} ) | ||
function fmTimepickerToggle() { | ||
return { | ||
restrict : "A", | ||
link : function postLink( scope, element, attributes ) { | ||
// Toggle the popup when the toggle button is clicked. | ||
element.bind( "click", function onClick() { | ||
if( scope.fmIsOpen ) { | ||
scope.focusInputElement(); | ||
scope.closePopup(); | ||
} else { | ||
// Focusing the input element will automatically open the popup | ||
scope.focusInputElement(); | ||
} | ||
} ); | ||
} | ||
}; | ||
} | ||
.directive( "fmTimepicker", [ | ||
"$timeout", function( $timeout ) { | ||
return { | ||
template : "<div>" + | ||
" <div class='input-group'>" + | ||
" <span class='input-group-btn' ng-if='style==\"sequential\"'>" + | ||
" <button type='button' class='btn {{btnClass}}' ng-click='decrement()' ng-disabled='activeIndex == 0 || disabled'>" + | ||
" <span class='glyphicon glyphicon-minus'></span>" + | ||
" </button>" + | ||
" </span>" + | ||
" <input type='text' class='form-control' ng-model='time' ng-keyup='handleKeyboardInput($event)' ng-change='update()' ng-disabled='disabled'>" + | ||
" <span class='input-group-btn'>" + | ||
" <button type='button' class='btn {{btnClass}}' ng-if='style==\"sequential\"' ng-click='increment()' ng-disabled='activeIndex == largestPossibleIndex || disabled'>" + | ||
" <span class='glyphicon glyphicon-plus'></span>" + | ||
" </button>" + | ||
" <button type='button' class='btn {{btnClass}}' ng-if='style==\"dropdown\"' ng-class='{active:isOpen}' fm-timepicker-toggle ng-disabled='disabled'>" + | ||
" <span class='glyphicon glyphicon-time'></span>" + | ||
" </button>" + | ||
" </span>" + | ||
" </div>" + | ||
" <div class='dropdown' ng-if='style==\"dropdown\" && isOpen' ng-class='{open:isOpen}'>" + | ||
" <ul class='dropdown-menu form-control' style='height:auto; max-height:160px; overflow-y:scroll;' ng-mousedown=\"handleListClick($event)\">" + | ||
// Fill an empty array with time values between start and end time with the given interval, then iterate over that array. | ||
" <li ng-repeat='time in ( $parent.dropDownOptions = ( [] | fmTimeInterval:startTime:endTime:interval ) )' ng-click='select(time,$index)' ng-class='{active:(activeIndex==$index)}'>" + | ||
// For each item, check if it is the last item. If it is, communicate the index to a method in the scope. | ||
" {{$last?largestPossibleIndexIs($index):angular.noop()}}" + | ||
// Render a link into the list item, with the formatted time value. | ||
" <a href='#' ng-click='preventDefault($event)'>{{time|fmTimeFormat:format}}</a>" + | ||
" </li>" + | ||
" </ul>" + | ||
" </div>" + | ||
"</div>", | ||
replace : true, | ||
restrict : "EA", | ||
scope : { | ||
ngModel : "=", | ||
format : "=?", | ||
startTime : "=?", | ||
endTime : "=?", | ||
reference : "=?", | ||
interval : "=?", | ||
largeInterval : "=?", | ||
isOpen : "=?", | ||
style : "=?", | ||
strict : "=?", | ||
btnClass : "=?", | ||
disabled : "=?" | ||
}, | ||
controller : "fmTimepickerController", | ||
require : "ngModel", | ||
link : function postLink( scope, element, attributes, controller ) { | ||
// Watch our input parameters and re-validate our view when they change. | ||
scope.$watchCollection( "[startTime,endTime,interval,strict]", function() { | ||
scope.constrainToReference(); | ||
validateView(); | ||
} ); | ||
/* @ngInject */ | ||
function fmTimepicker( $timeout ) { | ||
return { | ||
templateUrl : "fmTimepicker.html", | ||
replace : true, | ||
restrict : "EA", | ||
scope : { | ||
ngModel : "=", | ||
fmFormat : "=?", | ||
fmStartTime : "=?", | ||
fmEndTime : "=?", | ||
fmReference : "=?", | ||
fmInterval : "=?", | ||
fmLargeInterval : "=?", | ||
fmIsOpen : "=?", | ||
fmStyle : "=?", | ||
fmStrict : "=?", | ||
fmBtnClass : "=?", | ||
fmDisabled : "=?" | ||
}, | ||
controller : "fmTimepickerController", | ||
require : "ngModel", | ||
link : function postLink( scope, element, attributes, controller ) { | ||
// Watch our input parameters and re-validate our view when they change. | ||
scope.$watchCollection( "[fmStartTime,fmEndTime,fmInterval,fmStrict]", function inputWatcher() { | ||
scope.constrainToReference(); | ||
validateView(); | ||
} ); | ||
// Watch all time related parameters. | ||
scope.$watchCollection( "[startTime,endTime,interval,ngModel]", function() { | ||
// When they change, find the index of the element in the dropdown that relates to the current model value. | ||
scope.findActiveIndex( scope.ngModel ); | ||
} ); | ||
// Watch all time related parameters. | ||
scope.$watchCollection( "[fmStartTime,fmEndTime,fmInterval,ngModel]", function timeWatcher() { | ||
// When they change, find the index of the element in the dropdown that relates to the current model value. | ||
scope.findActiveIndex( scope.ngModel ); | ||
} ); | ||
/** | ||
* Invoked when we need to update the view due to a changed model value. | ||
*/ | ||
controller.$render = function() { | ||
// Convert the moment instance we got to a string in our desired format. | ||
var time = moment( controller.$modelValue ).format( scope.format ); | ||
// Check if the given time is valid. | ||
var timeValid = checkTimeValueValid( time ); | ||
if( scope.strict ) { | ||
timeValid = timeValid && checkTimeValueWithinBounds( time ) && checkTimeValueFitsInterval( | ||
time ); | ||
} | ||
/** | ||
* Invoked when we need to update the view due to a changed model value. | ||
*/ | ||
controller.$render = function() { | ||
// Convert the moment instance we got to a string in our desired format. | ||
var time = moment( controller.$modelValue ).format( scope.fmFormat ); | ||
// Check if the given time is valid. | ||
var timeValid = checkTimeValueValid( time ); | ||
if( scope.fmStrict ) { | ||
timeValid = timeValid && checkTimeValueWithinBounds( time ) && checkTimeValueFitsInterval( | ||
time ); | ||
} | ||
if( timeValid ) { | ||
// If the time is valid, store the time string in the scope used by the input box. | ||
scope.time = time; | ||
} else { | ||
throw new Error( "The provided time value is invalid." ); | ||
} | ||
}; | ||
if( timeValid ) { | ||
// If the time is valid, store the time string in the scope used by the input box. | ||
scope.time = time; | ||
} else { | ||
throw new Error( "The provided time value is invalid." ); | ||
} | ||
}; | ||
/** | ||
* Reset the validity of the directive. | ||
* @param {Boolean} to What to set the validity to? | ||
*/ | ||
function resetValidity( to ) { | ||
controller.$setValidity( "time", to ); | ||
controller.$setValidity( "bounds", to ); | ||
controller.$setValidity( "interval", to ); | ||
controller.$setValidity( "start", to ); | ||
controller.$setValidity( "end", to ); | ||
} | ||
/** | ||
* Reset the validity of the directive. | ||
* @param {Boolean} to What to set the validity to? | ||
*/ | ||
function resetValidity( to ) { | ||
controller.$setValidity( "time", to ); | ||
controller.$setValidity( "bounds", to ); | ||
controller.$setValidity( "interval", to ); | ||
controller.$setValidity( "start", to ); | ||
controller.$setValidity( "end", to ); | ||
} | ||
/** | ||
* Check if the value in the view is valid. | ||
* It has to represent a valid time in itself and it has to fit within the constraints defined through our input parameters. | ||
*/ | ||
function validateView() { | ||
resetValidity( true ); | ||
// Check if the string in the input box represents a valid date according to the rules set through parameters in our scope. | ||
var timeValid = checkTimeValueValid( scope.time ); | ||
if( scope.strict ) { | ||
timeValid = timeValid && checkTimeValueWithinBounds( scope.time ) && checkTimeValueFitsInterval( | ||
scope.time ); | ||
} | ||
/** | ||
* Check if the value in the view is valid. | ||
* It has to represent a valid time in itself and it has to fit within the constraints defined through our input parameters. | ||
*/ | ||
function validateView() { | ||
resetValidity( true ); | ||
// Check if the string in the input box represents a valid date according to the rules set through parameters in our scope. | ||
var timeValid = checkTimeValueValid( scope.time ); | ||
if( scope.fmStrict ) { | ||
timeValid = timeValid && checkTimeValueWithinBounds( scope.time ) && checkTimeValueFitsInterval( | ||
scope.time ); | ||
} | ||
if( !scope.startTime.isValid() ) { | ||
controller.$setValidity( "start", false ); | ||
} | ||
if( !scope.endTime.isValid() ) { | ||
controller.$setValidity( "end", false ); | ||
} | ||
if( !scope.fmStartTime.isValid() ) { | ||
controller.$setValidity( "start", false ); | ||
} | ||
if( !scope.fmEndTime.isValid() ) { | ||
controller.$setValidity( "end", false ); | ||
} | ||
if( timeValid ) { | ||
// If the string is valid, convert it to a moment instance, store in the model and... | ||
var newTime; | ||
if( moment.tz ) { | ||
newTime = moment.tz( | ||
scope.time, | ||
scope.format, | ||
scope.reference.tz() ); | ||
} else { | ||
newTime = moment( scope.time, scope.format ); | ||
} | ||
newTime = scope.constrainToReference( newTime ); | ||
controller.$setViewValue( newTime ); | ||
// ...convert it back to a string in our desired format. | ||
// This allows the user to input any partial format that moment accepts and we'll convert it to the format we expect. | ||
if( moment.tz ) { | ||
scope.time = moment.tz( | ||
scope.time, | ||
scope.format, | ||
scope.reference.tz() ).format( scope.format ); | ||
} else { | ||
scope.time = moment( scope.time, scope.format ).format( scope.format ); | ||
} | ||
} | ||
if( timeValid ) { | ||
// If the string is valid, convert it to a moment instance, store in the model and... | ||
var newTime; | ||
if( moment.tz ) { | ||
newTime = moment.tz( | ||
scope.time, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ); | ||
} else { | ||
newTime = moment( scope.time, scope.fmFormat ); | ||
} | ||
/** | ||
* Check if a given string represents a valid time in our expected format. | ||
* @param {String} timeString The timestamp is the expected format. | ||
* @returns {boolean} true if the string is a valid time; false otherwise. | ||
*/ | ||
function checkTimeValueValid( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.format, | ||
scope.reference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.format ) : moment.invalid(); | ||
} | ||
if( !time.isValid() ) { | ||
controller.$setValidity( "time", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "time", true ); | ||
return true; | ||
} | ||
newTime = scope.constrainToReference( newTime ); | ||
controller.$setViewValue( newTime ); | ||
// ...convert it back to a string in our desired format. | ||
// This allows the user to input any partial format that moment accepts and we'll convert it to the format we expect. | ||
if( moment.tz ) { | ||
scope.time = moment.tz( | ||
scope.time, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ).format( scope.fmFormat ); | ||
} else { | ||
scope.time = moment( scope.time, scope.fmFormat ).format( scope.fmFormat ); | ||
} | ||
} | ||
} | ||
/** | ||
* Check if a given string represents a time within the bounds specified through our start and end times. | ||
* @param {String} timeString The timestamp is the expected format. | ||
* @returns {boolean} true if the string represents a valid time and the time is within the defined bounds; false otherwise. | ||
*/ | ||
function checkTimeValueWithinBounds( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.format, | ||
scope.reference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.format ) : moment.invalid(); | ||
} | ||
time = scope.constrainToReference( time ); | ||
if( !time.isValid() || time.isBefore( scope.startTime ) || time.isAfter( scope.endTime ) ) { | ||
controller.$setValidity( "bounds", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "bounds", true ); | ||
return true; | ||
} | ||
} | ||
/** | ||
* Check if a given string represents a valid time in our expected format. | ||
* @param {String} timeString The timestamp is the expected format. | ||
* @returns {boolean} true if the string is a valid time; false otherwise. | ||
*/ | ||
function checkTimeValueValid( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.fmFormat ) : moment.invalid(); | ||
} | ||
if( !time.isValid() ) { | ||
controller.$setValidity( "time", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "time", true ); | ||
return true; | ||
} | ||
} | ||
/** | ||
* Check if a given string represents a time that lies on a the boundary of a time interval. | ||
* @param {String} timeString The timestamp in the expected format. | ||
* @returns {boolean} true if the string represents a valid time and that time lies on an interval boundary; false otherwise. | ||
*/ | ||
function checkTimeValueFitsInterval( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.format, | ||
scope.reference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.format ) : moment.invalid(); | ||
} | ||
// Check first if the time string could be parsed as a valid timestamp. | ||
var isValid = time.isValid(); | ||
if( isValid ) { | ||
// Calculate the amount of milliseconds that passed since the specified start time. | ||
var durationSinceStartTime = time.diff( scope.startTime ); | ||
// Calculate how many milliseconds are within the given time interval. | ||
var intervalMilliseconds = scope.interval.asMilliseconds(); | ||
// Check if the modulo operation has a remainder. | ||
isValid = ( 0 === ( durationSinceStartTime % intervalMilliseconds ) ); | ||
} | ||
/** | ||
* Check if a given string represents a time within the bounds specified through our start and end times. | ||
* @param {String} timeString The timestamp is the expected format. | ||
* @returns {boolean} true if the string represents a valid time and the time is within the defined bounds; false otherwise. | ||
*/ | ||
function checkTimeValueWithinBounds( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.fmFormat ) : moment.invalid(); | ||
} | ||
time = scope.constrainToReference( time ); | ||
if( !time.isValid() || time.isBefore( scope.fmStartTime ) || time.isAfter( scope.fmEndTime ) ) { | ||
controller.$setValidity( "bounds", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "bounds", true ); | ||
return true; | ||
} | ||
} | ||
if( !isValid ) { | ||
controller.$setValidity( "interval", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "interval", true ); | ||
return true; | ||
} | ||
} | ||
/** | ||
* Check if a given string represents a time that lies on a the boundary of a time interval. | ||
* @param {String} timeString The timestamp in the expected format. | ||
* @returns {boolean} true if the string represents a valid time and that time lies on an interval boundary; false otherwise. | ||
*/ | ||
function checkTimeValueFitsInterval( timeString ) { | ||
var time; | ||
if( moment.tz ) { | ||
time = timeString ? moment.tz( | ||
timeString, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ) : moment.invalid(); | ||
} else { | ||
time = timeString ? moment( timeString, scope.fmFormat ) : moment.invalid(); | ||
} | ||
// Check first if the time string could be parsed as a valid timestamp. | ||
var isValid = time.isValid(); | ||
if( isValid ) { | ||
// Calculate the amount of milliseconds that passed since the specified start time. | ||
var durationSinceStartTime = time.diff( scope.fmStartTime ); | ||
// Calculate how many milliseconds are within the given time interval. | ||
var intervalMilliseconds = scope.fmInterval.asMilliseconds(); | ||
// Check if the modulo operation has a remainder. | ||
isValid = ( 0 === ( durationSinceStartTime % intervalMilliseconds ) ); | ||
} | ||
function ensureUpdatedView() { | ||
$timeout( function() { | ||
scope.$apply(); | ||
} ); | ||
if( !isValid ) { | ||
controller.$setValidity( "interval", false ); | ||
controller.$setViewValue( null ); | ||
return false; | ||
} else { | ||
controller.$setValidity( "interval", true ); | ||
return true; | ||
} | ||
} | ||
// Scroll the selected list item into view if the popup is open. | ||
if( scope.isOpen ) { | ||
// Use $timeout to give the DOM time to catch up. | ||
$timeout( scrollSelectedItemIntoView ); | ||
} | ||
} | ||
function ensureUpdatedView() { | ||
$timeout( function runDigest() { | ||
scope.$apply(); | ||
} ); | ||
/** | ||
* Scroll the time that is currently selected into view. | ||
* This applies to the dropdown below the input element. | ||
*/ | ||
function scrollSelectedItemIntoView() { | ||
// Find the popup. | ||
var popupListElement = element.find( "ul" ); | ||
// Scroll it to the top, so that we can then get the correct relative offset for all list items. | ||
$( popupListElement ).scrollTop( 0 ); | ||
// Find the selected list item. | ||
var selectedListElement = $( "li.active", popupListElement ); | ||
// Retrieve offset from the top and height of the list element. | ||
var top = selectedListElement.length ? selectedListElement.position().top : 0; | ||
var height = selectedListElement.length ? selectedListElement.outerHeight( true ) : 0; | ||
// Scroll the list to bring the selected list element into the view. | ||
$( popupListElement ).scrollTop( top - height ); | ||
} | ||
// Scroll the selected list item into view if the popup is open. | ||
if( scope.fmIsOpen ) { | ||
// Use $timeout to give the DOM time to catch up. | ||
$timeout( scrollSelectedItemIntoView ); | ||
} | ||
} | ||
/** | ||
* Open the popup dropdown list. | ||
*/ | ||
function openPopup() { | ||
if( !scope.isOpen ) { | ||
scope.isOpen = true; | ||
scope.modelPreview = scope.ngModel ? scope.ngModel.clone() : scope.startTime.clone(); | ||
$timeout( ensureUpdatedView ); | ||
} | ||
} | ||
/** | ||
* Scroll the time that is currently selected into view. | ||
* This applies to the dropdown below the input element. | ||
*/ | ||
function scrollSelectedItemIntoView() { | ||
// Find the popup. | ||
var popupListElement = element.find( "ul" ); | ||
// Scroll it to the top, so that we can then get the correct relative offset for all list items. | ||
$( popupListElement ).scrollTop( 0 ); | ||
// Find the selected list item. | ||
var selectedListElement = $( "li.active", popupListElement ); | ||
// Retrieve offset from the top and height of the list element. | ||
var top = selectedListElement.length ? selectedListElement.position().top : 0; | ||
var height = selectedListElement.length ? selectedListElement.outerHeight( true ) : 0; | ||
// Scroll the list to bring the selected list element into the view. | ||
$( popupListElement ).scrollTop( top - height ); | ||
} | ||
// --------------- Scope methods --------------- | ||
/** | ||
* Open the popup dropdown list. | ||
*/ | ||
function openPopup() { | ||
if( !scope.fmIsOpen ) { | ||
scope.fmIsOpen = true; | ||
scope.modelPreview = scope.ngModel ? scope.ngModel.clone() : scope.fmStartTime.clone(); | ||
$timeout( ensureUpdatedView ); | ||
} | ||
} | ||
/** | ||
* Close the popup dropdown list. | ||
*/ | ||
scope.closePopup = function( delayed ) { | ||
if( delayed ) { | ||
// Delay closing the popup by 200ms to ensure selection of | ||
// list items can happen before the popup is hidden. | ||
$timeout( | ||
function() { | ||
scope.isOpen = false; | ||
}, 200 ); | ||
} else { | ||
scope.isOpen = false; | ||
$timeout( ensureUpdatedView ); | ||
} | ||
}; | ||
// --------------- Scope methods --------------- | ||
/** | ||
* This function is meant to handle clicking on the time input | ||
* box if it is already focused. Previously nothing would happen | ||
* and you would have to click the button or leave focus and | ||
* reclick to get the popup to open again. Adding this as a click | ||
* event makes it pop open again even if the input is focused. | ||
*/ | ||
scope.handleInputClick = function handleInputClick( $event ) { | ||
// bail if we aren't doing a dropdown | ||
if (scope.style !== "dropdown") { | ||
return; | ||
} | ||
/** | ||
* Close the popup dropdown list. | ||
*/ | ||
scope.closePopup = function( delayed ) { | ||
if( delayed ) { | ||
// Delay closing the popup by 200ms to ensure selection of | ||
// list items can happen before the popup is hidden. | ||
$timeout( | ||
function closeDropdown() { | ||
scope.fmIsOpen = false; | ||
}, 200 ); | ||
} else { | ||
scope.fmIsOpen = false; | ||
$timeout( ensureUpdatedView ); | ||
} | ||
}; | ||
openPopup(); | ||
}; | ||
scope.handleListClick = function handleListClick( $event ) { | ||
// When the list scrollbar is clicked, this can cause the list to lose focus. | ||
// Preventing the default behavior here has no undesired effects, it just stops | ||
// the input from losing focus. | ||
$event.preventDefault(); | ||
return false; | ||
}; | ||
scope.handleListClick = function handleListClick( $event ) { | ||
// When the list scrollbar is clicked, this can cause the list to lose focus. | ||
// Preventing the default behavior here has no undesired effects, it just stops | ||
// the input from losing focus. | ||
$event.preventDefault(); | ||
return false; | ||
}; | ||
/** | ||
* Selects a given timestamp as the new value of the timepicker. | ||
* @param {Number} timestamp UNIX timestamp | ||
* @param {Number} elementIndex The index of the time element in the dropdown list. | ||
*/ | ||
scope.select = function select( timestamp, elementIndex ) { | ||
// Construct a moment instance from the UNIX offset. | ||
var time; | ||
if( moment.tz && scope.fmReference.tz() ) { | ||
time = moment( timestamp ).tz( scope.fmReference.tz() ); | ||
} else { | ||
time = moment( timestamp ); | ||
} | ||
// Format the time to store it in the input box. | ||
scope.time = time.format( scope.fmFormat ); | ||
/** | ||
* Selects a given timestamp as the new value of the timepicker. | ||
* @param {Number} timestamp UNIX timestamp | ||
* @param {Number} elementIndex The index of the time element in the dropdown list. | ||
*/ | ||
scope.select = function( timestamp, elementIndex ) { | ||
// Construct a moment instance from the UNIX offset. | ||
var time; | ||
if( moment.tz && scope.reference.tz() ) { | ||
time = moment( timestamp ).tz( scope.reference.tz() ); | ||
} else { | ||
time = moment( timestamp ); | ||
} | ||
// Format the time to store it in the input box. | ||
scope.time = time.format( scope.format ); | ||
// Store the selected index | ||
scope.activeIndex = elementIndex; | ||
// Store the selected index | ||
scope.activeIndex = elementIndex; | ||
scope.update(); | ||
scope.closePopup(); | ||
}; | ||
scope.update(); | ||
scope.closePopup(); | ||
}; | ||
scope.increment = function increment() { | ||
if( scope.fmIsOpen ) { | ||
scope.modelPreview.add( scope.fmInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
} else { | ||
scope.ngModel.add( scope.fmInterval ); | ||
scope.ngModel = scope.ensureTimeIsWithinBounds( scope.ngModel ); | ||
scope.time = scope.ngModel.format( scope.fmFormat ); | ||
} | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, scope.activeIndex + 1 ); | ||
}; | ||
scope.increment = function() { | ||
if( scope.isOpen ) { | ||
scope.modelPreview.add( scope.interval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
} else { | ||
scope.ngModel.add( scope.interval ); | ||
scope.ngModel = scope.ensureTimeIsWithinBounds( scope.ngModel ); | ||
scope.time = scope.ngModel.format( scope.format ); | ||
} | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, scope.activeIndex + 1 ); | ||
}; | ||
scope.decrement = function decrement() { | ||
if( scope.fmIsOpen ) { | ||
scope.modelPreview.subtract( scope.fmInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
} else { | ||
scope.ngModel.subtract( scope.fmInterval ); | ||
scope.ngModel = scope.ensureTimeIsWithinBounds( scope.ngModel ); | ||
scope.time = scope.ngModel.format( scope.fmFormat ); | ||
} | ||
scope.activeIndex = Math.max( 0, scope.activeIndex - 1 ); | ||
}; | ||
scope.decrement = function() { | ||
if( scope.isOpen ) { | ||
scope.modelPreview.subtract( scope.interval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
} else { | ||
scope.ngModel.subtract( scope.interval ); | ||
scope.ngModel = scope.ensureTimeIsWithinBounds( scope.ngModel ); | ||
scope.time = scope.ngModel.format( scope.format ); | ||
} | ||
scope.activeIndex = Math.max( 0, scope.activeIndex - 1 ); | ||
}; | ||
/** | ||
* Check if the value in the input control is a valid timestamp. | ||
*/ | ||
scope.update = function update() { | ||
var timeValid = checkTimeValueValid( scope.time ) && checkTimeValueWithinBounds( scope.time ); | ||
if( timeValid ) { | ||
var newTime; | ||
if( moment.tz ) { | ||
newTime = moment.tz( scope.time, | ||
scope.fmFormat, | ||
scope.fmReference.tz() ); | ||
} else { | ||
newTime = moment( scope.time, scope.fmFormat ); | ||
} | ||
newTime = scope.constrainToReference( newTime ); | ||
controller.$setViewValue( newTime ); | ||
} | ||
}; | ||
/** | ||
* Check if the value in the input control is a valid timestamp. | ||
*/ | ||
scope.update = function() { | ||
var timeValid = checkTimeValueValid( scope.time ) && checkTimeValueWithinBounds( scope.time ); | ||
if( timeValid ) { | ||
var newTime; | ||
if( moment.tz ) { | ||
newTime = moment.tz( scope.time, | ||
scope.format, | ||
scope.reference.tz() ); | ||
} else { | ||
newTime = moment( scope.time, scope.format ); | ||
} | ||
newTime = scope.constrainToReference( newTime ); | ||
controller.$setViewValue( newTime ); | ||
scope.handleKeyboardInput = function handleKeyboardInput( event ) { | ||
switch( event.keyCode ) { | ||
case 13: | ||
// Enter | ||
if( scope.modelPreview ) { | ||
scope.ngModel = scope.modelPreview; | ||
scope.fmIsOpen = false; | ||
} | ||
}; | ||
break; | ||
case 27: | ||
// Escape | ||
scope.closePopup(); | ||
break; | ||
case 33: | ||
// Page up | ||
openPopup(); | ||
scope.modelPreview.subtract( scope.fmLargeInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
scope.activeIndex = Math.max( 0, | ||
scope.activeIndex - scope.largeIntervalIndexJump ); | ||
break; | ||
case 34: | ||
// Page down | ||
openPopup(); | ||
scope.modelPreview.add( scope.fmLargeInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, | ||
scope.activeIndex + scope.largeIntervalIndexJump ); | ||
break; | ||
case 38: | ||
// Up arrow | ||
openPopup(); | ||
scope.decrement(); | ||
break; | ||
case 40: | ||
// Down arrow | ||
openPopup(); | ||
scope.increment(); | ||
break; | ||
default: | ||
} | ||
$timeout( ensureUpdatedView ); | ||
}; | ||
scope.handleKeyboardInput = function( event ) { | ||
switch( event.keyCode ) { | ||
case 13: | ||
// Enter | ||
if( scope.modelPreview ) { | ||
scope.ngModel = scope.modelPreview; | ||
scope.isOpen = false; | ||
} | ||
break; | ||
case 27: | ||
// Escape | ||
scope.closePopup(); | ||
break; | ||
case 33: | ||
// Page up | ||
openPopup(); | ||
scope.modelPreview.subtract( scope.largeInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
scope.activeIndex = Math.max( 0, | ||
scope.activeIndex - scope.largeIntervalIndexJump ); | ||
break; | ||
case 34: | ||
// Page down | ||
openPopup(); | ||
scope.modelPreview.add( scope.largeInterval ); | ||
scope.modelPreview = scope.ensureTimeIsWithinBounds( scope.modelPreview ); | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, | ||
scope.activeIndex + scope.largeIntervalIndexJump ); | ||
break; | ||
case 38: | ||
// Up arrow | ||
openPopup(); | ||
scope.decrement(); | ||
break; | ||
case 40: | ||
// Down arrow | ||
openPopup(); | ||
scope.increment(); | ||
break; | ||
default: | ||
} | ||
$timeout( ensureUpdatedView ); | ||
}; | ||
/** | ||
* Prevent default behavior from happening. | ||
* @param event | ||
*/ | ||
scope.preventDefault = function preventDefault( event ) { | ||
event.preventDefault(); | ||
}; | ||
/** | ||
* Prevent default behavior from happening. | ||
* @param event | ||
*/ | ||
scope.preventDefault = function( event ) { | ||
event.preventDefault(); | ||
}; | ||
/** | ||
* Remember the highest index of the existing list items. | ||
* We use this to constrain the possible values for the index that marks a list item as active. | ||
* @param {Number} index | ||
*/ | ||
scope.largestPossibleIndexIs = function largestPossibleIndexIs( index ) { | ||
scope.largestPossibleIndex = index; | ||
}; | ||
/** | ||
* Remember the highest index of the existing list items. | ||
* We use this to constrain the possible values for the index that marks a list item as active. | ||
* @param {Number} index | ||
*/ | ||
scope.largestPossibleIndexIs = function( index ) { | ||
scope.largestPossibleIndex = index; | ||
}; | ||
scope.focusInputElement = function focusInputElement() { | ||
$( inputElement ).focus(); | ||
}; | ||
scope.focusInputElement = function() { | ||
$( inputElement ).focus(); | ||
}; | ||
var inputElement = element.find( "input" ); | ||
var popupListElement = element.find( "ul" ); | ||
var inputElement = element.find( "input" ); | ||
var popupListElement = element.find( "ul" ); | ||
/** | ||
* Open the popup when the input box gets focus. | ||
*/ | ||
inputElement.bind( "focus", function onFocus() { | ||
// Without delay the popup can glitch close itself instantly after being opened. | ||
$timeout( openPopup, 150 ); | ||
scope.isFocused = true; | ||
} ); | ||
/** | ||
* Open the popup when the input box gets focus. | ||
*/ | ||
inputElement.bind( "focus", function() { | ||
// Without delay the popup can glitch close itself instantly after being opened. | ||
$timeout( openPopup, 150 ); | ||
scope.isFocused = true; | ||
} ); | ||
/** | ||
* Invoked when the input box loses focus. | ||
*/ | ||
inputElement.bind( "blur", function onBlur() { | ||
// Delay any action by 150ms | ||
$timeout( function checkFocusState() { | ||
// Check if we didn't get refocused in the meantime. | ||
// This can happen if the input box is selected and the user toggles the dropdown. | ||
// This would cause a hide and close in rapid succession, so don't do it. | ||
if( !$( inputElement ).is( ":focus" ) ) { | ||
scope.closePopup(); | ||
validateView(); | ||
} | ||
}, 150 ); | ||
scope.isFocused = false; | ||
} ); | ||
/** | ||
* Invoked when the input box loses focus. | ||
*/ | ||
inputElement.bind( "blur", function() { | ||
// Delay any action by 150ms | ||
$timeout( function() { | ||
// Check if we didn't get refocused in the meantime. | ||
// This can happen if the input box is selected and the user toggles the dropdown. | ||
// This would cause a hide and close in rapid succession, so don't do it. | ||
if( !$( inputElement ).is( ":focus" ) ) { | ||
scope.closePopup(); | ||
validateView(); | ||
} | ||
}, 150 ); | ||
scope.isFocused = false; | ||
} ); | ||
popupListElement.bind( "mousedown", function onMousedown( event ) { | ||
event.preventDefault(); | ||
} ); | ||
popupListElement.bind( "mousedown", function( event ) { | ||
if( typeof Hamster === "function" ) { | ||
Hamster( inputElement[ 0 ] ).wheel( function onMousewheel( event, delta, deltaX, deltaY ) { | ||
if( scope.isFocused ) { | ||
event.preventDefault(); | ||
} ); | ||
if( typeof Hamster === "function" ) { | ||
Hamster( inputElement[ 0 ] ).wheel( function( event, delta, deltaX, deltaY ) { | ||
if( scope.isFocused ) { | ||
event.preventDefault(); | ||
scope.activeIndex -= delta; | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, | ||
Math.max( 0, scope.activeIndex ) ); | ||
scope.activeIndex -= delta; | ||
scope.activeIndex = Math.min( scope.largestPossibleIndex, | ||
Math.max( 0, scope.activeIndex ) ); | ||
scope.select( scope.dropDownOptions[ scope.activeIndex ], scope.activeIndex ); | ||
$timeout( ensureUpdatedView ); | ||
} | ||
} ); | ||
scope.select( scope.dropDownOptions[ scope.activeIndex ], scope.activeIndex ); | ||
$timeout( ensureUpdatedView ); | ||
} | ||
} ); | ||
} | ||
} | ||
}; | ||
} | ||
] ); | ||
}; | ||
} | ||
fmTimepicker.$inject = ["$timeout"]; | ||
})(); |
@@ -1,5 +0,1 @@ | ||
<<<<<<< HEAD | ||
!function(){"use strict";try{angular.module("fm.components")}catch(a){angular.module("fm.components",[])}angular.module("fm.components").filter("fmTimeFormat",function(){return function(a,b){return"number"==typeof a&&(a=moment(a)),moment(a).format(b)}}).filter("fmTimeInterval",function(){return function(a,b,c,d){if(!b||!c)return a;b=moment(b),c=moment(c),d=d||moment.duration(30,"minutes");for(var e=b.clone();+c>=+e;e.add(d))a.push(+e);return a}}).controller("fmTimepickerController",["$scope",function(a){if(a.reference=a.reference?moment(a.reference):moment(),a.style=a.style||"dropdown",a.isOpen=a.isOpen||!1,a.format=a.format||"LT",a.startTime=a.startTime||moment(a.reference).startOf("day"),a.endTime=a.endTime||moment(a.reference).endOf("day"),a.interval=a.interval||moment.duration(30,"minutes"),a.largeInterval=a.largeInterval||moment.duration(60,"minutes"),a.strict=a.strict||!1,a.btnClass=a.btnClass||"btn-default",moment.tz&&(a.startTime.tz(a.reference.tz()),a.endTime.tz(a.reference.tz())),a.strict){var b=a.ngModel.valueOf(),c=a.interval.asMilliseconds();b-=b%c,b+=c,a.ngModel=moment(b)}a.constrainToReference=function(b){return b?(moment.tz&&b.tz(a.reference.tz()),b.isSame(a.reference,"day")||b.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),b):(a.startTime.isSame(a.reference,"day")||a.startTime.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),a.endTime.isSame(a.reference,"day")||a.endTime.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),a.ngModel&&!a.ngModel.isSame(a.reference,"day")&&a.ngModel.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),null)},a.constrainToReference(),a.ensureTimeIsWithinBounds=function(b){return b&&moment.isMoment(b)?b.isBefore(a.startTime)?moment(a.startTime):b.isAfter(a.endTime)?moment(a.endTime):b:b},a.ngModel=a.ensureTimeIsWithinBounds(a.ngModel),a.findActiveIndex=function(b){if(a.activeIndex=0,b)for(var c=a.startTime.clone();+c<=+a.endTime&&!c.isSame(b);c.add(a.interval),++a.activeIndex)if(c.isAfter(b)){a.strict&&(a.activeIndex=-1),a.activeIndex-=1;break}},a.largestPossibleIndex=Number.MAX_VALUE,a.largeIntervalIndexJump=Number.MAX_VALUE,a.findActiveIndex(a.ngModel),a.$watch("interval",function(b,c){b.asMilliseconds()<1&&(console.error("[fm-timepicker] Error: Supplied interval length is smaller than 1ms! Reverting to default."),a.interval=moment.duration(30,"minutes"))}),a.$watch("largeInterval",function(b,c){b.asMilliseconds()<10&&(console.error("[fm-timepicker] Error: Supplied large interval length is smaller than 10ms! Reverting to default."),a.largeInterval=moment.duration(60,"minutes"))}),a.$watchCollection("[interval,largeInterval]",function(b){var c=b[0],d=b[1],e=c.asMilliseconds(),f=d.asMilliseconds();0!==f%e&&(console.warn("[fm-timepicker] Warning: Large interval is not a multiple of interval! Using internally computed value instead."),a.largeInterval=moment.duration(5*e),f=a.largeInterval.asMilliseconds()),a.largeIntervalIndexJump=f/e})}]).directive("fmTimepickerToggle",function(){return{restrict:"A",link:function(a,b,c){b.bind("click",function(){a.isOpen?(a.focusInputElement(),a.closePopup()):a.focusInputElement()})}}}).directive("fmTimepicker",["$timeout",function(a){return{template:"<div> <div class='input-group'> <span class='input-group-btn' ng-if='style==\"sequential\"'> <button type='button' class='btn {{btnClass}}' ng-click='decrement()' ng-disabled='activeIndex==0'> <span class='glyphicon glyphicon-minus'></span> </button> </span> <input type='text' class='form-control' ng-model='time' ng-keyup='handleKeyboardInput($event)' ng-change='update()'> <span class='input-group-btn'> <button type='button' class='btn {{btnClass}}' ng-if='style==\"sequential\"' ng-click='increment()' ng-disabled='activeIndex==largestPossibleIndex'> <span class='glyphicon glyphicon-plus'></span> </button> <button type='button' class='btn {{btnClass}}' ng-if='style==\"dropdown\"' ng-class='{active:isOpen}' fm-timepicker-toggle> <span class='glyphicon glyphicon-time'></span> </button> </span> </div> <div class='dropdown' ng-if='style==\"dropdown\" && isOpen' ng-class='{open:isOpen}'> <ul class='dropdown-menu form-control' style='height:auto; max-height:160px; overflow-y:scroll;' ng-mousedown=\"handleListClick($event)\"> <li ng-repeat='time in ( $parent.dropDownOptions = ( [] | fmTimeInterval:startTime:endTime:interval ) )' ng-click='select(time,$index)' ng-class='{active:(activeIndex==$index)}'> {{$last?largestPossibleIndexIs($index):angular.noop()}} <a href='#' ng-click='preventDefault($event)'>{{time|fmTimeFormat:format}}</a> </li> </ul> </div></div>",replace:!0,restrict:"E",scope:{ngModel:"=",format:"=?",startTime:"=?",endTime:"=?",reference:"=?",interval:"=?",largeInterval:"=?",isOpen:"=?",style:"=?",strict:"=?",btnClass:"=?"},controller:"fmTimepickerController",require:"ngModel",link:function(b,c,d,e){function f(a){e.$setValidity("time",a),e.$setValidity("bounds",a),e.$setValidity("interval",a),e.$setValidity("start",a),e.$setValidity("end",a)}function g(){f(!0);var a=h(b.time);if(b.strict&&(a=a&&i(b.time)&&j(b.time)),b.startTime.isValid()||e.$setValidity("start",!1),b.endTime.isValid()||e.$setValidity("end",!1),a){var c;c=moment.tz?moment.tz(b.time,b.format,b.reference.tz()):moment(b.time,b.format),c=b.constrainToReference(c),e.$setViewValue(c),moment.tz?b.time=moment.tz(b.time,b.format,b.reference.tz()).format(b.format):b.time=moment(b.time,b.format).format(b.format)}}function h(a){var c;return c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid(),c.isValid()?(e.$setValidity("time",!0),!0):(e.$setValidity("time",!1),e.$setViewValue(null),!1)}function i(a){var c;return c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid(),c=b.constrainToReference(c),!c.isValid()||c.isBefore(b.startTime)||c.isAfter(b.endTime)?(e.$setValidity("bounds",!1),e.$setViewValue(null),!1):(e.$setValidity("bounds",!0),!0)}function j(a){var c;c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid();var d=c.isValid();if(d){var f=c.diff(b.startTime),g=b.interval.asMilliseconds();d=0===f%g}return d?(e.$setValidity("interval",!0),!0):(e.$setValidity("interval",!1),e.$setViewValue(null),!1)}function k(){a(function(){b.$apply()}),b.isOpen&&a(l)}function l(){var a=c.find("ul");$(a).scrollTop(0);var b=$("li.active",a),d=b.length?b.position().top:0,e=b.length?b.outerHeight(!0):0;$(a).scrollTop(d-e)}function m(){b.isOpen||(b.isOpen=!0,b.modelPreview=b.ngModel?b.ngModel.clone():b.startTime.clone(),a(k))}b.$watchCollection("[startTime,endTime,interval,strict]",function(){b.constrainToReference(),g()}),b.$watchCollection("[startTime,endTime,interval,ngModel]",function(){b.findActiveIndex(b.ngModel)}),e.$render=function(){var a=moment(e.$modelValue).format(b.format),c=h(a);if(b.strict&&(c=c&&i(a)&&j(a)),!c)throw new Error("The provided time value is invalid.");b.time=a},b.closePopup=function(c){c?a(function(){b.isOpen=!1},200):(b.isOpen=!1,a(k))},b.handleInputClick=function(a){"dropdown"===b.style&&m()},b.handleListClick=function(a){return a.preventDefault(),!1},b.select=function(a,c){var d;d=moment.tz?moment(a).tz(b.reference.tz()):moment(a),b.time=d.format(b.format),b.activeIndex=c,b.update(),b.closePopup()},b.increment=function(){b.isOpen?(b.modelPreview.add(b.interval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.add(b.interval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.format)),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+1)},b.decrement=function(){b.isOpen?(b.modelPreview.subtract(b.interval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.subtract(b.interval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.format)),b.activeIndex=Math.max(0,b.activeIndex-1)},b.update=function(){var a=h(b.time)&&i(b.time);if(a){var c;c=moment.tz?moment.tz(b.time,b.format,b.reference.tz()):moment(b.time,b.format),c=b.constrainToReference(c),e.$setViewValue(c)}},b.handleKeyboardInput=function(c){switch(c.keyCode){case 13:b.modelPreview&&(b.ngModel=b.modelPreview,b.isOpen=!1);break;case 27:b.closePopup();break;case 33:m(),b.modelPreview.subtract(b.largeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.max(0,b.activeIndex-b.largeIntervalIndexJump);break;case 34:m(),b.modelPreview.add(b.largeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+b.largeIntervalIndexJump);break;case 38:m(),b.decrement();break;case 40:m(),b.increment()}a(k)},b.preventDefault=function(a){a.preventDefault()},b.largestPossibleIndexIs=function(a){b.largestPossibleIndex=a},b.focusInputElement=function(){$(n).focus()};var n=c.find("input"),o=c.find("ul");n.bind("focus",function(){a(m,150),b.isFocused=!0}),n.bind("blur",function(){a(function(){$(n).is(":focus")||(b.closePopup(),g())},150),b.isFocused=!1}),o.bind("mousedown",function(a){a.preventDefault()}),"function"==typeof Hamster&&Hamster(n[0]).wheel(function(c,d,e,f){b.isFocused&&(c.preventDefault(),b.activeIndex-=d,b.activeIndex=Math.min(b.largestPossibleIndex,Math.max(0,b.activeIndex)),b.select(b.dropDownOptions[b.activeIndex],b.activeIndex),a(k))})}}}])}(); | ||
======= | ||
!function(){"use strict";try{angular.module("fm.components")}catch(a){angular.module("fm.components",[])}angular.module("fm.components").filter("fmTimeFormat",function(){return function(a,b){return"number"==typeof a&&(a=moment(a)),moment(a).format(b)}}).filter("fmTimeInterval",function(){return function(a,b,c,d){if(!b||!c)return a;b=moment(b),c=moment(c),d=d||moment.duration(30,"minutes");for(var e=b.clone();+c>=+e;e.add(d))a.push(+e);return a}}).controller("fmTimepickerController",["$scope",function(a){if(a.reference=a.reference?moment(a.reference):moment(),a.style=a.style||"dropdown",a.isOpen=a.isOpen||!1,a.format=a.format||"LT",a.startTime=a.startTime||moment(a.reference).startOf("day"),a.endTime=a.endTime||moment(a.reference).endOf("day"),a.interval=a.interval||moment.duration(30,"minutes"),a.largeInterval=a.largeInterval||moment.duration(60,"minutes"),a.strict=a.strict||!1,a.btnClass=a.btnClass||"btn-default",moment.tz&&(a.startTime.tz(a.reference.tz()),a.endTime.tz(a.reference.tz())),a.strict){var b=a.ngModel.valueOf(),c=a.interval.asMilliseconds();b-=b%c,b+=c,a.ngModel=moment(b)}a.constrainToReference=function(b){return b?(moment.tz&&b.tz(a.reference.tz()),b.isSame(a.reference,"day")||b.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),b):(a.startTime.isSame(a.reference,"day")||a.startTime.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),a.endTime.isSame(a.reference,"day")||a.endTime.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),a.ngModel&&!a.ngModel.isSame(a.reference,"day")&&a.ngModel.year(a.reference.year()).month(a.reference.month()).date(a.reference.date()),null)},a.constrainToReference(),a.ensureTimeIsWithinBounds=function(b){return b&&moment.isMoment(b)?b.isBefore(a.startTime)?moment(a.startTime):b.isAfter(a.endTime)?moment(a.endTime):b:b},a.ngModel=a.ensureTimeIsWithinBounds(a.ngModel),a.findActiveIndex=function(b){if(a.activeIndex=0,b)for(var c=a.startTime.clone();+c<=+a.endTime&&!c.isSame(b);c.add(a.interval),++a.activeIndex)if(c.isAfter(b)){a.strict&&(a.activeIndex=-1),a.activeIndex-=1;break}},a.largestPossibleIndex=Number.MAX_VALUE,a.largeIntervalIndexJump=Number.MAX_VALUE,a.findActiveIndex(a.ngModel),a.$watch("interval",function(b,c){b.asMilliseconds()<1&&(console.error("[fm-timepicker] Error: Supplied interval length is smaller than 1ms! Reverting to default."),a.interval=moment.duration(30,"minutes"))}),a.$watch("largeInterval",function(b,c){b.asMilliseconds()<10&&(console.error("[fm-timepicker] Error: Supplied large interval length is smaller than 10ms! Reverting to default."),a.largeInterval=moment.duration(60,"minutes"))}),a.$watchCollection("[interval,largeInterval]",function(b){var c=b[0],d=b[1],e=c.asMilliseconds(),f=d.asMilliseconds();0!==f%e&&(console.warn("[fm-timepicker] Warning: Large interval is not a multiple of interval! Using internally computed value instead."),a.largeInterval=moment.duration(5*e),f=a.largeInterval.asMilliseconds()),a.largeIntervalIndexJump=f/e})}]).directive("fmTimepickerToggle",function(){return{restrict:"A",link:function(a,b,c){b.bind("click",function(){a.isOpen?(a.focusInputElement(),a.closePopup()):a.focusInputElement()})}}}).directive("fmTimepicker",["$timeout",function(a){return{template:"<div> <div class='input-group'> <span class='input-group-btn' ng-if='style==\"sequential\"'> <button type='button' class='btn {{btnClass}}' ng-click='decrement()' ng-disabled='activeIndex == 0 || disabled'> <span class='glyphicon glyphicon-minus'></span> </button> </span> <input type='text' class='form-control' ng-model='time' ng-keyup='handleKeyboardInput($event)' ng-change='update()' ng-disabled='disabled'> <span class='input-group-btn'> <button type='button' class='btn {{btnClass}}' ng-if='style==\"sequential\"' ng-click='increment()' ng-disabled='activeIndex == largestPossibleIndex || disabled'> <span class='glyphicon glyphicon-plus'></span> </button> <button type='button' class='btn {{btnClass}}' ng-if='style==\"dropdown\"' ng-class='{active:isOpen}' fm-timepicker-toggle ng-disabled='disabled'> <span class='glyphicon glyphicon-time'></span> </button> </span> </div> <div class='dropdown' ng-if='style==\"dropdown\" && isOpen' ng-class='{open:isOpen}'> <ul class='dropdown-menu form-control' style='height:auto; max-height:160px; overflow-y:scroll;' ng-mousedown=\"handleListClick($event)\"> <li ng-repeat='time in ( $parent.dropDownOptions = ( [] | fmTimeInterval:startTime:endTime:interval ) )' ng-click='select(time,$index)' ng-class='{active:(activeIndex==$index)}'> {{$last?largestPossibleIndexIs($index):angular.noop()}} <a href='#' ng-click='preventDefault($event)'>{{time|fmTimeFormat:format}}</a> </li> </ul> </div></div>",replace:!0,restrict:"EA",scope:{ngModel:"=",format:"=?",startTime:"=?",endTime:"=?",reference:"=?",interval:"=?",largeInterval:"=?",isOpen:"=?",style:"=?",strict:"=?",btnClass:"=?",disabled:"=?"},controller:"fmTimepickerController",require:"ngModel",link:function(b,c,d,e){function f(a){e.$setValidity("time",a),e.$setValidity("bounds",a),e.$setValidity("interval",a),e.$setValidity("start",a),e.$setValidity("end",a)}function g(){f(!0);var a=h(b.time);if(b.strict&&(a=a&&i(b.time)&&j(b.time)),b.startTime.isValid()||e.$setValidity("start",!1),b.endTime.isValid()||e.$setValidity("end",!1),a){var c;c=moment.tz?moment.tz(b.time,b.format,b.reference.tz()):moment(b.time,b.format),c=b.constrainToReference(c),e.$setViewValue(c),moment.tz?b.time=moment.tz(b.time,b.format,b.reference.tz()).format(b.format):b.time=moment(b.time,b.format).format(b.format)}}function h(a){var c;return c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid(),c.isValid()?(e.$setValidity("time",!0),!0):(e.$setValidity("time",!1),e.$setViewValue(null),!1)}function i(a){var c;return c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid(),c=b.constrainToReference(c),!c.isValid()||c.isBefore(b.startTime)||c.isAfter(b.endTime)?(e.$setValidity("bounds",!1),e.$setViewValue(null),!1):(e.$setValidity("bounds",!0),!0)}function j(a){var c;c=moment.tz?a?moment.tz(a,b.format,b.reference.tz()):moment.invalid():a?moment(a,b.format):moment.invalid();var d=c.isValid();if(d){var f=c.diff(b.startTime),g=b.interval.asMilliseconds();d=0===f%g}return d?(e.$setValidity("interval",!0),!0):(e.$setValidity("interval",!1),e.$setViewValue(null),!1)}function k(){a(function(){b.$apply()}),b.isOpen&&a(l)}function l(){var a=c.find("ul");$(a).scrollTop(0);var b=$("li.active",a),d=b.length?b.position().top:0,e=b.length?b.outerHeight(!0):0;$(a).scrollTop(d-e)}function m(){b.isOpen||(b.isOpen=!0,b.modelPreview=b.ngModel?b.ngModel.clone():b.startTime.clone(),a(k))}b.$watchCollection("[startTime,endTime,interval,strict]",function(){b.constrainToReference(),g()}),b.$watchCollection("[startTime,endTime,interval,ngModel]",function(){b.findActiveIndex(b.ngModel)}),e.$render=function(){var a=moment(e.$modelValue).format(b.format),c=h(a);if(b.strict&&(c=c&&i(a)&&j(a)),!c)throw new Error("The provided time value is invalid.");b.time=a},b.closePopup=function(c){c?a(function(){b.isOpen=!1},200):(b.isOpen=!1,a(k))},b.handleListClick=function(a){return a.preventDefault(),!1},b.select=function(a,c){var d;d=moment.tz&&b.reference.tz()?moment(a).tz(b.reference.tz()):moment(a),b.time=d.format(b.format),b.activeIndex=c,b.update(),b.closePopup()},b.increment=function(){b.isOpen?(b.modelPreview.add(b.interval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.add(b.interval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.format)),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+1)},b.decrement=function(){b.isOpen?(b.modelPreview.subtract(b.interval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.subtract(b.interval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.format)),b.activeIndex=Math.max(0,b.activeIndex-1)},b.update=function(){var a=h(b.time)&&i(b.time);if(a){var c;c=moment.tz?moment.tz(b.time,b.format,b.reference.tz()):moment(b.time,b.format),c=b.constrainToReference(c),e.$setViewValue(c)}},b.handleKeyboardInput=function(c){switch(c.keyCode){case 13:b.modelPreview&&(b.ngModel=b.modelPreview,b.isOpen=!1);break;case 27:b.closePopup();break;case 33:m(),b.modelPreview.subtract(b.largeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.max(0,b.activeIndex-b.largeIntervalIndexJump);break;case 34:m(),b.modelPreview.add(b.largeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+b.largeIntervalIndexJump);break;case 38:m(),b.decrement();break;case 40:m(),b.increment()}a(k)},b.preventDefault=function(a){a.preventDefault()},b.largestPossibleIndexIs=function(a){b.largestPossibleIndex=a},b.focusInputElement=function(){$(n).focus()};var n=c.find("input"),o=c.find("ul");n.bind("focus",function(){a(m,150),b.isFocused=!0}),n.bind("blur",function(){a(function(){$(n).is(":focus")||(b.closePopup(),g())},150),b.isFocused=!1}),o.bind("mousedown",function(a){a.preventDefault()}),"function"==typeof Hamster&&Hamster(n[0]).wheel(function(c,d,e,f){b.isFocused&&(c.preventDefault(),b.activeIndex-=d,b.activeIndex=Math.min(b.largestPossibleIndex,Math.max(0,b.activeIndex)),b.select(b.dropDownOptions[b.activeIndex],b.activeIndex),a(k))})}}}])}(); | ||
>>>>>>> release/3.2.0 | ||
!function(){"use strict";function a(){return function(a,b){return"number"==typeof a&&(a=moment(a)),moment(a).format(b)}}function b(){return function(a,b,c,d){if(!b||!c)return a;b=moment(b),c=moment(c),d=d||moment.duration(30,"minutes");for(var e=b.clone();+c>=+e;e.add(d))a.push(+e);return a}}function c(a){if(a.fmReference=a.fmReference?moment(a.fmReference):moment(),a.fmStyle=a.fmStyle||"dropdown",a.fmIsOpen=a.fmIsOpen||!1,a.fmFormat=a.fmFormat||"LT",a.fmStartTime=a.fmStartTime||moment(a.fmReference).startOf("day"),a.fmEndTime=a.fmEndTime||moment(a.fmReference).endOf("day"),a.fmInterval=a.fmInterval||moment.duration(30,"minutes"),a.fmLargeInterval=a.fmLargeInterval||moment.duration(60,"minutes"),a.fmStrict=a.fmStrict||!1,a.fmBtnClass=a.fmBtnClass||"btn-default",moment.tz&&(a.fmStartTime.tz(a.fmReference.tz()),a.fmEndTime.tz(a.fmReference.tz())),a.fmStrict){var b=a.ngModel.valueOf(),c=a.fmInterval.asMilliseconds();b-=b%c,b+=c,a.ngModel=moment(b)}a.constrainToReference=function(b){return b?(moment.tz&&b.tz(a.fmReference.tz()),b.isSame(a.fmReference,"day")||b.year(a.fmReference.year()).month(a.fmReference.month()).date(a.fmReference.date()),b):(a.fmStartTime.isSame(a.fmReference,"day")||a.fmStartTime.year(a.fmReference.year()).month(a.fmReference.month()).date(a.fmReference.date()),a.fmEndTime.isSame(a.fmReference,"day")||a.fmEndTime.year(a.fmReference.year()).month(a.fmReference.month()).date(a.fmReference.date()),a.ngModel&&!a.ngModel.isSame(a.fmReference,"day")&&a.ngModel.year(a.fmReference.year()).month(a.fmReference.month()).date(a.fmReference.date()),null)},a.constrainToReference(),a.ensureTimeIsWithinBounds=function(b){return b&&moment.isMoment(b)?b.isBefore(a.fmStartTime)?moment(a.fmStartTime):b.isAfter(a.fmEndTime)?moment(a.fmEndTime):b:b},a.ngModel=a.ensureTimeIsWithinBounds(a.ngModel),a.findActiveIndex=function(b){if(a.activeIndex=0,b)for(var c=a.fmStartTime.clone();+c<=+a.fmEndTime&&!c.isSame(b);c.add(a.fmInterval),++a.activeIndex)if(c.isAfter(b)){a.fmStrict&&(a.activeIndex=-1),a.activeIndex-=1;break}},a.largestPossibleIndex=Number.MAX_VALUE,a.largeIntervalIndexJump=Number.MAX_VALUE,a.findActiveIndex(a.ngModel),a.$watch("fmInterval",function(b,c){b.asMilliseconds()<1&&(console.error("[fm-timepicker] Error: Supplied interval length is smaller than 1ms! Reverting to default."),a.fmInterval=moment.duration(30,"minutes"))}),a.$watch("fmLargeInterval",function(b,c){b.asMilliseconds()<10&&(console.error("[fm-timepicker] Error: Supplied large interval length is smaller than 10ms! Reverting to default."),a.fmLargeInterval=moment.duration(60,"minutes"))}),a.$watchCollection("[fmInterval,fmLargeInterval]",function(b){var c=b[0],d=b[1],e=c.asMilliseconds(),f=d.asMilliseconds();0!==f%e&&(console.warn("[fm-timepicker] Warning: Large interval is not a multiple of interval! Using internally computed value instead."),a.fmLargeInterval=moment.duration(5*e),f=a.fmLargeInterval.asMilliseconds()),a.largeIntervalIndexJump=f/e})}function d(){return{restrict:"A",link:function(a,b,c){b.bind("click",function(){a.fmIsOpen?(a.focusInputElement(),a.closePopup()):a.focusInputElement()})}}}function e(a){return{templateUrl:"fmTimepicker.html",replace:!0,restrict:"EA",scope:{ngModel:"=",fmFormat:"=?",fmStartTime:"=?",fmEndTime:"=?",fmReference:"=?",fmInterval:"=?",fmLargeInterval:"=?",fmIsOpen:"=?",fmStyle:"=?",fmStrict:"=?",fmBtnClass:"=?",fmDisabled:"=?"},controller:"fmTimepickerController",require:"ngModel",link:function(b,c,d,e){function f(a){e.$setValidity("time",a),e.$setValidity("bounds",a),e.$setValidity("interval",a),e.$setValidity("start",a),e.$setValidity("end",a)}function g(){f(!0);var a=h(b.time);if(b.fmStrict&&(a=a&&i(b.time)&&j(b.time)),b.fmStartTime.isValid()||e.$setValidity("start",!1),b.fmEndTime.isValid()||e.$setValidity("end",!1),a){var c;c=moment.tz?moment.tz(b.time,b.fmFormat,b.fmReference.tz()):moment(b.time,b.fmFormat),c=b.constrainToReference(c),e.$setViewValue(c),moment.tz?b.time=moment.tz(b.time,b.fmFormat,b.fmReference.tz()).format(b.fmFormat):b.time=moment(b.time,b.fmFormat).format(b.fmFormat)}}function h(a){var c;return c=moment.tz?a?moment.tz(a,b.fmFormat,b.fmReference.tz()):moment.invalid():a?moment(a,b.fmFormat):moment.invalid(),c.isValid()?(e.$setValidity("time",!0),!0):(e.$setValidity("time",!1),e.$setViewValue(null),!1)}function i(a){var c;return c=moment.tz?a?moment.tz(a,b.fmFormat,b.fmReference.tz()):moment.invalid():a?moment(a,b.fmFormat):moment.invalid(),c=b.constrainToReference(c),!c.isValid()||c.isBefore(b.fmStartTime)||c.isAfter(b.fmEndTime)?(e.$setValidity("bounds",!1),e.$setViewValue(null),!1):(e.$setValidity("bounds",!0),!0)}function j(a){var c;c=moment.tz?a?moment.tz(a,b.fmFormat,b.fmReference.tz()):moment.invalid():a?moment(a,b.fmFormat):moment.invalid();var d=c.isValid();if(d){var f=c.diff(b.fmStartTime),g=b.fmInterval.asMilliseconds();d=0===f%g}return d?(e.$setValidity("interval",!0),!0):(e.$setValidity("interval",!1),e.$setViewValue(null),!1)}function k(){a(function(){b.$apply()}),b.fmIsOpen&&a(l)}function l(){var a=c.find("ul");$(a).scrollTop(0);var b=$("li.active",a),d=b.length?b.position().top:0,e=b.length?b.outerHeight(!0):0;$(a).scrollTop(d-e)}function m(){b.fmIsOpen||(b.fmIsOpen=!0,b.modelPreview=b.ngModel?b.ngModel.clone():b.fmStartTime.clone(),a(k))}b.$watchCollection("[fmStartTime,fmEndTime,fmInterval,fmStrict]",function(){b.constrainToReference(),g()}),b.$watchCollection("[fmStartTime,fmEndTime,fmInterval,ngModel]",function(){b.findActiveIndex(b.ngModel)}),e.$render=function(){var a=moment(e.$modelValue).format(b.fmFormat),c=h(a);if(b.fmStrict&&(c=c&&i(a)&&j(a)),!c)throw new Error("The provided time value is invalid.");b.time=a},b.closePopup=function(c){c?a(function(){b.fmIsOpen=!1},200):(b.fmIsOpen=!1,a(k))},b.handleListClick=function(a){return a.preventDefault(),!1},b.select=function(a,c){var d;d=moment.tz&&b.fmReference.tz()?moment(a).tz(b.fmReference.tz()):moment(a),b.time=d.format(b.fmFormat),b.activeIndex=c,b.update(),b.closePopup()},b.increment=function(){b.fmIsOpen?(b.modelPreview.add(b.fmInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.add(b.fmInterval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.fmFormat)),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+1)},b.decrement=function(){b.fmIsOpen?(b.modelPreview.subtract(b.fmInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview)):(b.ngModel.subtract(b.fmInterval),b.ngModel=b.ensureTimeIsWithinBounds(b.ngModel),b.time=b.ngModel.format(b.fmFormat)),b.activeIndex=Math.max(0,b.activeIndex-1)},b.update=function(){var a=h(b.time)&&i(b.time);if(a){var c;c=moment.tz?moment.tz(b.time,b.fmFormat,b.fmReference.tz()):moment(b.time,b.fmFormat),c=b.constrainToReference(c),e.$setViewValue(c)}},b.handleKeyboardInput=function(c){switch(c.keyCode){case 13:b.modelPreview&&(b.ngModel=b.modelPreview,b.fmIsOpen=!1);break;case 27:b.closePopup();break;case 33:m(),b.modelPreview.subtract(b.fmLargeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.max(0,b.activeIndex-b.largeIntervalIndexJump);break;case 34:m(),b.modelPreview.add(b.fmLargeInterval),b.modelPreview=b.ensureTimeIsWithinBounds(b.modelPreview),b.activeIndex=Math.min(b.largestPossibleIndex,b.activeIndex+b.largeIntervalIndexJump);break;case 38:m(),b.decrement();break;case 40:m(),b.increment()}a(k)},b.preventDefault=function(a){a.preventDefault()},b.largestPossibleIndexIs=function(a){b.largestPossibleIndex=a},b.focusInputElement=function(){$(n).focus()};var n=c.find("input"),o=c.find("ul");n.bind("focus",function(){a(m,150),b.isFocused=!0}),n.bind("blur",function(){a(function(){$(n).is(":focus")||(b.closePopup(),g())},150),b.isFocused=!1}),o.bind("mousedown",function(a){a.preventDefault()}),"function"==typeof Hamster&&Hamster(n[0]).wheel(function(c,d,e,f){b.isFocused&&(c.preventDefault(),b.activeIndex-=d,b.activeIndex=Math.min(b.largestPossibleIndex,Math.max(0,b.activeIndex)),b.select(b.dropDownOptions[b.activeIndex],b.activeIndex),a(k))})}}}angular.module("fmTimepicker",[]),angular.module("fmTimepicker").filter("fmTimeFormat",a).filter("fmTimeInterval",b).controller("fmTimepickerController",c).directive("fmTimepickerToggle",d).directive("fmTimepicker",e),c.$inject=["$scope"],e.$inject=["$timeout"]}(); |
@@ -8,3 +8,3 @@ { | ||
], | ||
"main": "dist/fm-timepicker.js", | ||
"main": "dist/fm-timepicker.tpls.js", | ||
"license": "MIT", | ||
@@ -11,0 +11,0 @@ "private": false, |
{ | ||
"name": "fm-timepicker", | ||
"version": "4.0.0", | ||
"version": "4.0.1", | ||
"description": "FairManager Time Picker Component", | ||
@@ -5,0 +5,0 @@ "main": "src/fm-timepicker.js", |
@@ -289,3 +289,3 @@ /** | ||
*/ | ||
controller.$render = function() { | ||
controller.$render = function render() { | ||
// Convert the moment instance we got to a string in our desired format. | ||
@@ -292,0 +292,0 @@ var time = moment( controller.$modelValue ).format( scope.fmFormat ); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
225842
38
3905