| angular.module('ui.bootstrap.timepicker', []) |
| |
| .constant('uibTimepickerConfig', { |
| hourStep: 1, |
| minuteStep: 1, |
| showMeridian: true, |
| meridians: null, |
| readonlyInput: false, |
| mousewheel: true, |
| arrowkeys: true, |
| showSpinners: true |
| }) |
| |
| .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { |
| var selected = new Date(), |
| ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl |
| meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; |
| |
| $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; |
| $element.removeAttr('tabindex'); |
| |
| this.init = function(ngModelCtrl_, inputs) { |
| ngModelCtrl = ngModelCtrl_; |
| ngModelCtrl.$render = this.render; |
| |
| ngModelCtrl.$formatters.unshift(function(modelValue) { |
| return modelValue ? new Date(modelValue) : null; |
| }); |
| |
| var hoursInputEl = inputs.eq(0), |
| minutesInputEl = inputs.eq(1); |
| |
| var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; |
| if (mousewheel) { |
| this.setupMousewheelEvents(hoursInputEl, minutesInputEl); |
| } |
| |
| var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; |
| if (arrowkeys) { |
| this.setupArrowkeyEvents(hoursInputEl, minutesInputEl); |
| } |
| |
| $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; |
| this.setupInputEvents(hoursInputEl, minutesInputEl); |
| }; |
| |
| var hourStep = timepickerConfig.hourStep; |
| if ($attrs.hourStep) { |
| $scope.$parent.$watch($parse($attrs.hourStep), function(value) { |
| hourStep = parseInt(value, 10); |
| }); |
| } |
| |
| var minuteStep = timepickerConfig.minuteStep; |
| if ($attrs.minuteStep) { |
| $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { |
| minuteStep = parseInt(value, 10); |
| }); |
| } |
| |
| var min; |
| $scope.$parent.$watch($parse($attrs.min), function(value) { |
| var dt = new Date(value); |
| min = isNaN(dt) ? undefined : dt; |
| }); |
| |
| var max; |
| $scope.$parent.$watch($parse($attrs.max), function(value) { |
| var dt = new Date(value); |
| max = isNaN(dt) ? undefined : dt; |
| }); |
| |
| $scope.noIncrementHours = function() { |
| var incrementedSelected = addMinutes(selected, hourStep * 60); |
| return incrementedSelected > max || |
| (incrementedSelected < selected && incrementedSelected < min); |
| }; |
| |
| $scope.noDecrementHours = function() { |
| var decrementedSelected = addMinutes(selected, -hourStep * 60); |
| return decrementedSelected < min || |
| (decrementedSelected > selected && decrementedSelected > max); |
| }; |
| |
| $scope.noIncrementMinutes = function() { |
| var incrementedSelected = addMinutes(selected, minuteStep); |
| return incrementedSelected > max || |
| (incrementedSelected < selected && incrementedSelected < min); |
| }; |
| |
| $scope.noDecrementMinutes = function() { |
| var decrementedSelected = addMinutes(selected, -minuteStep); |
| return decrementedSelected < min || |
| (decrementedSelected > selected && decrementedSelected > max); |
| }; |
| |
| $scope.noToggleMeridian = function() { |
| if (selected.getHours() < 13) { |
| return addMinutes(selected, 12 * 60) > max; |
| } else { |
| return addMinutes(selected, -12 * 60) < min; |
| } |
| }; |
| |
| // 12H / 24H mode |
| $scope.showMeridian = timepickerConfig.showMeridian; |
| if ($attrs.showMeridian) { |
| $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { |
| $scope.showMeridian = !!value; |
| |
| if (ngModelCtrl.$error.time) { |
| // Evaluate from template |
| var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); |
| if (angular.isDefined(hours) && angular.isDefined(minutes)) { |
| selected.setHours(hours); |
| refresh(); |
| } |
| } else { |
| updateTemplate(); |
| } |
| }); |
| } |
| |
| // Get $scope.hours in 24H mode if valid |
| function getHoursFromTemplate() { |
| var hours = parseInt($scope.hours, 10); |
| var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); |
| if (!valid) { |
| return undefined; |
| } |
| |
| if ($scope.showMeridian) { |
| if (hours === 12) { |
| hours = 0; |
| } |
| if ($scope.meridian === meridians[1]) { |
| hours = hours + 12; |
| } |
| } |
| return hours; |
| } |
| |
| function getMinutesFromTemplate() { |
| var minutes = parseInt($scope.minutes, 10); |
| return (minutes >= 0 && minutes < 60) ? minutes : undefined; |
| } |
| |
| function pad(value) { |
| return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString(); |
| } |
| |
| // Respond on mousewheel spin |
| this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) { |
| var isScrollingUp = function(e) { |
| if (e.originalEvent) { |
| e = e.originalEvent; |
| } |
| //pick correct delta variable depending on event |
| var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; |
| return (e.detail || delta > 0); |
| }; |
| |
| hoursInputEl.bind('mousewheel wheel', function(e) { |
| $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); |
| e.preventDefault(); |
| }); |
| |
| minutesInputEl.bind('mousewheel wheel', function(e) { |
| $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); |
| e.preventDefault(); |
| }); |
| |
| }; |
| |
| // Respond on up/down arrowkeys |
| this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) { |
| hoursInputEl.bind('keydown', function(e) { |
| if (e.which === 38) { // up |
| e.preventDefault(); |
| $scope.incrementHours(); |
| $scope.$apply(); |
| } else if (e.which === 40) { // down |
| e.preventDefault(); |
| $scope.decrementHours(); |
| $scope.$apply(); |
| } |
| }); |
| |
| minutesInputEl.bind('keydown', function(e) { |
| if (e.which === 38) { // up |
| e.preventDefault(); |
| $scope.incrementMinutes(); |
| $scope.$apply(); |
| } else if (e.which === 40) { // down |
| e.preventDefault(); |
| $scope.decrementMinutes(); |
| $scope.$apply(); |
| } |
| }); |
| }; |
| |
| this.setupInputEvents = function(hoursInputEl, minutesInputEl) { |
| if ($scope.readonlyInput) { |
| $scope.updateHours = angular.noop; |
| $scope.updateMinutes = angular.noop; |
| return; |
| } |
| |
| var invalidate = function(invalidHours, invalidMinutes) { |
| ngModelCtrl.$setViewValue(null); |
| ngModelCtrl.$setValidity('time', false); |
| if (angular.isDefined(invalidHours)) { |
| $scope.invalidHours = invalidHours; |
| } |
| if (angular.isDefined(invalidMinutes)) { |
| $scope.invalidMinutes = invalidMinutes; |
| } |
| }; |
| |
| $scope.updateHours = function() { |
| var hours = getHoursFromTemplate(), |
| minutes = getMinutesFromTemplate(); |
| |
| if (angular.isDefined(hours) && angular.isDefined(minutes)) { |
| selected.setHours(hours); |
| if (selected < min || selected > max) { |
| invalidate(true); |
| } else { |
| refresh('h'); |
| } |
| } else { |
| invalidate(true); |
| } |
| }; |
| |
| hoursInputEl.bind('blur', function(e) { |
| if (!$scope.invalidHours && $scope.hours < 10) { |
| $scope.$apply(function() { |
| $scope.hours = pad($scope.hours); |
| }); |
| } |
| }); |
| |
| $scope.updateMinutes = function() { |
| var minutes = getMinutesFromTemplate(), |
| hours = getHoursFromTemplate(); |
| |
| if (angular.isDefined(minutes) && angular.isDefined(hours)) { |
| selected.setMinutes(minutes); |
| if (selected < min || selected > max) { |
| invalidate(undefined, true); |
| } else { |
| refresh('m'); |
| } |
| } else { |
| invalidate(undefined, true); |
| } |
| }; |
| |
| minutesInputEl.bind('blur', function(e) { |
| if (!$scope.invalidMinutes && $scope.minutes < 10) { |
| $scope.$apply(function() { |
| $scope.minutes = pad($scope.minutes); |
| }); |
| } |
| }); |
| |
| }; |
| |
| this.render = function() { |
| var date = ngModelCtrl.$viewValue; |
| |
| if (isNaN(date)) { |
| ngModelCtrl.$setValidity('time', false); |
| $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); |
| } else { |
| if (date) { |
| selected = date; |
| } |
| |
| if (selected < min || selected > max) { |
| ngModelCtrl.$setValidity('time', false); |
| $scope.invalidHours = true; |
| $scope.invalidMinutes = true; |
| } else { |
| makeValid(); |
| } |
| updateTemplate(); |
| } |
| }; |
| |
| // Call internally when we know that model is valid. |
| function refresh(keyboardChange) { |
| makeValid(); |
| ngModelCtrl.$setViewValue(new Date(selected)); |
| updateTemplate(keyboardChange); |
| } |
| |
| function makeValid() { |
| ngModelCtrl.$setValidity('time', true); |
| $scope.invalidHours = false; |
| $scope.invalidMinutes = false; |
| } |
| |
| function updateTemplate(keyboardChange) { |
| var hours = selected.getHours(), minutes = selected.getMinutes(); |
| |
| if ($scope.showMeridian) { |
| hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system |
| } |
| |
| $scope.hours = keyboardChange === 'h' ? hours : pad(hours); |
| if (keyboardChange !== 'm') { |
| $scope.minutes = pad(minutes); |
| } |
| $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; |
| } |
| |
| function addMinutes(date, minutes) { |
| var dt = new Date(date.getTime() + minutes * 60000); |
| var newDate = new Date(date); |
| newDate.setHours(dt.getHours(), dt.getMinutes()); |
| return newDate; |
| } |
| |
| function addMinutesToSelected(minutes) { |
| selected = addMinutes(selected, minutes); |
| refresh(); |
| } |
| |
| $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? |
| $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; |
| |
| $scope.incrementHours = function() { |
| if (!$scope.noIncrementHours()) { |
| addMinutesToSelected(hourStep * 60); |
| } |
| }; |
| |
| $scope.decrementHours = function() { |
| if (!$scope.noDecrementHours()) { |
| addMinutesToSelected(-hourStep * 60); |
| } |
| }; |
| |
| $scope.incrementMinutes = function() { |
| if (!$scope.noIncrementMinutes()) { |
| addMinutesToSelected(minuteStep); |
| } |
| }; |
| |
| $scope.decrementMinutes = function() { |
| if (!$scope.noDecrementMinutes()) { |
| addMinutesToSelected(-minuteStep); |
| } |
| }; |
| |
| $scope.toggleMeridian = function() { |
| if (!$scope.noToggleMeridian()) { |
| addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1)); |
| } |
| }; |
| }]) |
| |
| .directive('uibTimepicker', function() { |
| return { |
| restrict: 'EA', |
| require: ['uibTimepicker', '?^ngModel'], |
| controller: 'UibTimepickerController', |
| controllerAs: 'timepicker', |
| replace: true, |
| scope: {}, |
| templateUrl: function(element, attrs) { |
| return attrs.templateUrl || 'template/timepicker/timepicker.html'; |
| }, |
| link: function(scope, element, attrs, ctrls) { |
| var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; |
| |
| if (ngModelCtrl) { |
| timepickerCtrl.init(ngModelCtrl, element.find('input')); |
| } |
| } |
| }; |
| }); |
| |
| /* Deprecated timepicker below */ |
| |
| angular.module('ui.bootstrap.timepicker') |
| |
| .value('$timepickerSuppressWarning', false) |
| |
| .controller('TimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', '$timepickerSuppressWarning', function($scope, $element, $attrs, $controller, $log, $timepickerSuppressWarning) { |
| if (!$timepickerSuppressWarning) { |
| $log.warn('TimepickerController is now deprecated. Use UibTimepickerController instead.'); |
| } |
| |
| angular.extend(this, $controller('UibTimepickerController', { |
| $scope: $scope, |
| $element: $element, |
| $attrs: $attrs |
| })); |
| }]) |
| |
| .directive('timepicker', ['$log', '$timepickerSuppressWarning', function($log, $timepickerSuppressWarning) { |
| return { |
| restrict: 'EA', |
| require: ['timepicker', '?^ngModel'], |
| controller: 'TimepickerController', |
| controllerAs: 'timepicker', |
| replace: true, |
| scope: {}, |
| templateUrl: function(element, attrs) { |
| return attrs.templateUrl || 'template/timepicker/timepicker.html'; |
| }, |
| link: function(scope, element, attrs, ctrls) { |
| if (!$timepickerSuppressWarning) { |
| $log.warn('timepicker is now deprecated. Use uib-timepicker instead.'); |
| } |
| var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; |
| |
| if (ngModelCtrl) { |
| timepickerCtrl.init(ngModelCtrl, element.find('input')); |
| } |
| } |
| }; |
| }]); |