blob: 0c5173edc43350f32eec115aedcf459123bf030e [file] [log] [blame]
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'));
}
}
};
}]);