blob: 7cf87593edb64dff3521f77c3c32645747f25079 [file] [log] [blame]
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
.value('$datepickerSuppressError', false)
.constant('uibDatepickerConfig', {
formatDay: 'dd',
formatMonth: 'MMMM',
formatYear: 'yyyy',
formatDayHeader: 'EEE',
formatDayTitle: 'MMMM yyyy',
formatMonthTitle: 'yyyy',
datepickerMode: 'day',
minMode: 'day',
maxMode: 'year',
showWeeks: true,
startingDay: 0,
yearRange: 20,
minDate: null,
maxDate: null,
shortcutPropagation: false
})
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) {
var self = this,
ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
// Modes chain
this.modes = ['day', 'month', 'year'];
// Configuration attributes
angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
});
// Watchable date attributes
angular.forEach(['minDate', 'maxDate'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($parse($attrs[key]), function(value) {
self[key] = value ? new Date(value) : null;
self.refreshView();
});
} else {
self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
}
});
angular.forEach(['minMode', 'maxMode'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($parse($attrs[key]), function(value) {
self[key] = angular.isDefined(value) ? value : $attrs[key];
$scope[key] = self[key];
if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
$scope.datepickerMode = self[key];
}
});
} else {
self[key] = datepickerConfig[key] || null;
$scope[key] = self[key];
}
});
$scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
if (angular.isDefined($attrs.initDate)) {
this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
$scope.$parent.$watch($attrs.initDate, function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
self.activeDate = initDate;
self.refreshView();
}
});
} else {
this.activeDate = new Date();
}
$scope.isActive = function(dateObject) {
if (self.compare(dateObject.date, self.activeDate) === 0) {
$scope.activeDateId = dateObject.uid;
return true;
}
return false;
};
this.init = function(ngModelCtrl_) {
ngModelCtrl = ngModelCtrl_;
ngModelCtrl.$render = function() {
self.render();
};
};
this.render = function() {
if (ngModelCtrl.$viewValue) {
var date = new Date(ngModelCtrl.$viewValue),
isValid = !isNaN(date);
if (isValid) {
this.activeDate = date;
} else if (!$datepickerSuppressError) {
$log.error('Datepicker 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.');
}
}
this.refreshView();
};
this.refreshView = function() {
if (this.element) {
this._refreshView();
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
}
};
this.createDateObject = function(date, format) {
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
return {
date: date,
label: dateFilter(date, format),
selected: model && this.compare(date, model) === 0,
disabled: this.isDisabled(date),
current: this.compare(date, new Date()) === 0,
customClass: this.customClass(date)
};
};
this.isDisabled = function(date) {
return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
};
this.customClass = function(date) {
return $scope.customClass({date: date, mode: $scope.datepickerMode});
};
// Split array into smaller arrays
this.split = function(arr, size) {
var arrays = [];
while (arr.length > 0) {
arrays.push(arr.splice(0, size));
}
return arrays;
};
$scope.select = function(date) {
if ($scope.datepickerMode === self.minMode) {
var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
ngModelCtrl.$setViewValue(dt);
ngModelCtrl.$render();
} else {
self.activeDate = date;
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
}
};
$scope.move = function(direction) {
var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
month = self.activeDate.getMonth() + direction * (self.step.months || 0);
self.activeDate.setFullYear(year, month, 1);
self.refreshView();
};
$scope.toggleMode = function(direction) {
direction = direction || 1;
if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
return;
}
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
};
// Key event mapper
$scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
var focusElement = function() {
self.element[0].focus();
};
// Listen for focus requests from popup directive
$scope.$on('uib:datepicker.focus', focusElement);
$scope.keydown = function(evt) {
var key = $scope.keys[evt.which];
if (!key || evt.shiftKey || evt.altKey) {
return;
}
evt.preventDefault();
if (!self.shortcutPropagation) {
evt.stopPropagation();
}
if (key === 'enter' || key === 'space') {
if (self.isDisabled(self.activeDate)) {
return; // do nothing
}
$scope.select(self.activeDate);
} else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
$scope.toggleMode(key === 'up' ? 1 : -1);
} else {
self.handleKeyDown(key, evt);
self.refreshView();
}
};
}])
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
this.step = { months: 1 };
this.element = $element;
function getDaysInMonth(year, month) {
return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
}
this.init = function(ctrl) {
angular.extend(ctrl, this);
scope.showWeeks = ctrl.showWeeks;
ctrl.refreshView();
};
this.getDates = function(startDate, n) {
var dates = new Array(n), current = new Date(startDate), i = 0, date;
while (i < n) {
date = new Date(current);
dates[i++] = date;
current.setDate(current.getDate() + 1);
}
return dates;
};
this._refreshView = function() {
var year = this.activeDate.getFullYear(),
month = this.activeDate.getMonth(),
firstDayOfMonth = new Date(this.activeDate);
firstDayOfMonth.setFullYear(year, month, 1);
var difference = this.startingDay - firstDayOfMonth.getDay(),
numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
firstDate = new Date(firstDayOfMonth);
if (numDisplayedFromPreviousMonth > 0) {
firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
}
// 42 is the number of days on a six-month calendar
var days = this.getDates(firstDate, 42);
for (var i = 0; i < 42; i ++) {
days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
secondary: days[i].getMonth() !== month,
uid: scope.uniqueId + '-' + i
});
}
scope.labels = new Array(7);
for (var j = 0; j < 7; j++) {
scope.labels[j] = {
abbr: dateFilter(days[j].date, this.formatDayHeader),
full: dateFilter(days[j].date, 'EEEE')
};
}
scope.title = dateFilter(this.activeDate, this.formatDayTitle);
scope.rows = this.split(days, 7);
if (scope.showWeeks) {
scope.weekNumbers = [];
var thursdayIndex = (4 + 7 - this.startingDay) % 7,
numWeeks = scope.rows.length;
for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
scope.weekNumbers.push(
getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
}
}
};
this.compare = function(date1, date2) {
return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
};
function getISO8601WeekNumber(date) {
var checkDate = new Date(date);
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
var time = checkDate.getTime();
checkDate.setMonth(0); // Compare with Jan 1
checkDate.setDate(1);
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
}
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getDate();
if (key === 'left') {
date = date - 1; // up
} else if (key === 'up') {
date = date - 7; // down
} else if (key === 'right') {
date = date + 1; // down
} else if (key === 'down') {
date = date + 7;
} else if (key === 'pageup' || key === 'pagedown') {
var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
this.activeDate.setMonth(month, 1);
date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
} else if (key === 'home') {
date = 1;
} else if (key === 'end') {
date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
}
this.activeDate.setDate(date);
};
}])
.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
this.step = { years: 1 };
this.element = $element;
this.init = function(ctrl) {
angular.extend(ctrl, this);
ctrl.refreshView();
};
this._refreshView = function() {
var months = new Array(12),
year = this.activeDate.getFullYear(),
date;
for (var i = 0; i < 12; i++) {
date = new Date(this.activeDate);
date.setFullYear(year, i, 1);
months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
uid: scope.uniqueId + '-' + i
});
}
scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
scope.rows = this.split(months, 3);
};
this.compare = function(date1, date2) {
return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
};
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getMonth();
if (key === 'left') {
date = date - 1; // up
} else if (key === 'up') {
date = date - 3; // down
} else if (key === 'right') {
date = date + 1; // down
} else if (key === 'down') {
date = date + 3;
} else if (key === 'pageup' || key === 'pagedown') {
var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
this.activeDate.setFullYear(year);
} else if (key === 'home') {
date = 0;
} else if (key === 'end') {
date = 11;
}
this.activeDate.setMonth(date);
};
}])
.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
var range;
this.element = $element;
function getStartingYear(year) {
return parseInt((year - 1) / range, 10) * range + 1;
}
this.yearpickerInit = function() {
range = this.yearRange;
this.step = { years: range };
};
this._refreshView = function() {
var years = new Array(range), date;
for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
date = new Date(this.activeDate);
date.setFullYear(start + i, 0, 1);
years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
uid: scope.uniqueId + '-' + i
});
}
scope.title = [years[0].label, years[range - 1].label].join(' - ');
scope.rows = this.split(years, 5);
};
this.compare = function(date1, date2) {
return date1.getFullYear() - date2.getFullYear();
};
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getFullYear();
if (key === 'left') {
date = date - 1; // up
} else if (key === 'up') {
date = date - 5; // down
} else if (key === 'right') {
date = date + 1; // down
} else if (key === 'down') {
date = date + 5;
} else if (key === 'pageup' || key === 'pagedown') {
date += (key === 'pageup' ? - 1 : 1) * this.step.years;
} else if (key === 'home') {
date = getStartingYear(this.activeDate.getFullYear());
} else if (key === 'end') {
date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
}
this.activeDate.setFullYear(date);
};
}])
.directive('uibDatepicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/datepicker.html';
},
scope: {
datepickerMode: '=?',
dateDisabled: '&',
customClass: '&',
shortcutPropagation: '&?'
},
require: ['uibDatepicker', '^ngModel'],
controller: 'UibDatepickerController',
controllerAs: 'datepicker',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
datepickerCtrl.init(ngModelCtrl);
}
};
})
.directive('uibDaypicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/day.html';
},
require: ['^?uibDatepicker', 'uibDaypicker', '^?datepicker'],
controller: 'UibDaypickerController',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0] || ctrls[2],
daypickerCtrl = ctrls[1];
daypickerCtrl.init(datepickerCtrl);
}
};
})
.directive('uibMonthpicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/month.html';
},
require: ['^?uibDatepicker', 'uibMonthpicker', '^?datepicker'],
controller: 'UibMonthpickerController',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0] || ctrls[2],
monthpickerCtrl = ctrls[1];
monthpickerCtrl.init(datepickerCtrl);
}
};
})
.directive('uibYearpicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/year.html';
},
require: ['^?uibDatepicker', 'uibYearpicker', '^?datepicker'],
controller: 'UibYearpickerController',
link: function(scope, element, attrs, ctrls) {
var ctrl = ctrls[0] || ctrls[2];
angular.extend(ctrl, ctrls[1]);
ctrl.yearpickerInit();
ctrl.refreshView();
}
};
})
.constant('uibDatepickerPopupConfig', {
datepickerPopup: 'yyyy-MM-dd',
datepickerPopupTemplateUrl: 'template/datepicker/popup.html',
datepickerTemplateUrl: 'template/datepicker/datepicker.html',
html5Types: {
date: 'yyyy-MM-dd',
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
'month': 'yyyy-MM'
},
currentText: 'Today',
clearText: 'Clear',
closeText: 'Done',
closeOnDateSelection: true,
appendToBody: false,
showButtonBar: true,
onOpenFocus: true
})
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout',
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
var self = this;
var cache = {},
isHtml5DateInput = false;
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
ngModel, $popup;
scope.watchData = {};
this.init = function(_ngModel_) {
ngModel = _ngModel_;
closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
if (datepickerPopupConfig.html5Types[attrs.type]) {
dateFormat = datepickerPopupConfig.html5Types[attrs.type];
isHtml5DateInput = true;
} else {
dateFormat = attrs.datepickerPopup || attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if (newDateFormat !== dateFormat) {
dateFormat = newDateFormat;
ngModel.$modelValue = null;
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
}
});
}
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
if (isHtml5DateInput && attrs.datepickerPopup) {
throw new Error('HTML5 date input types do not support custom formats.');
}
// popup element used to display calendar
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
popupEl.attr({
'ng-model': 'date',
'ng-change': 'dateSelection(date)',
'template-url': datepickerPopupTemplateUrl
});
// datepicker element
datepickerEl = angular.element(popupEl.children()[0]);
datepickerEl.attr('template-url', datepickerTemplateUrl);
if (isHtml5DateInput) {
if (attrs.type === 'month') {
datepickerEl.attr('datepicker-mode', '"month"');
datepickerEl.attr('min-mode', 'month');
}
}
if (attrs.datepickerOptions) {
var options = scope.$parent.$eval(attrs.datepickerOptions);
if (options && options.initDate) {
scope.initDate = options.initDate;
datepickerEl.attr('init-date', 'initDate');
delete options.initDate;
}
angular.forEach(options, function(value, option) {
datepickerEl.attr(cameltoDash(option), value);
});
}
angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
if (attrs[key]) {
var getAttribute = $parse(attrs[key]);
scope.$parent.$watch(getAttribute, function(value) {
scope.watchData[key] = value;
if (key === 'minDate' || key === 'maxDate') {
cache[key] = new Date(value);
}
});
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
// Propagate changes from datepicker to outside
if (key === 'datepickerMode') {
var setAttribute = getAttribute.assign;
scope.$watch('watchData.' + key, function(value, oldvalue) {
if (angular.isFunction(setAttribute) && value !== oldvalue) {
setAttribute(scope.$parent, value);
}
});
}
}
});
if (attrs.dateDisabled) {
datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
}
if (attrs.showWeeks) {
datepickerEl.attr('show-weeks', attrs.showWeeks);
}
if (attrs.customClass) {
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
}
if (!isHtml5DateInput) {
// Internal API to maintain the correct ng-invalid-[key] class
ngModel.$$parserName = 'date';
ngModel.$validators.date = validator;
ngModel.$parsers.unshift(parseDate);
ngModel.$formatters.push(function(value) {
scope.date = value;
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
});
} else {
ngModel.$formatters.push(function(value) {
scope.date = value;
return value;
});
}
// Detect changes in the view from the text box
ngModel.$viewChangeListeners.push(function() {
scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date);
});
element.bind('keydown', inputKeydownBind);
$popup = $compile(popupEl)(scope);
// Prevent jQuery cache memory leak (template is now redundant after linking)
popupEl.remove();
if (appendToBody) {
$document.find('body').append($popup);
} else {
element.after($popup);
}
scope.$on('$destroy', function() {
if (scope.isOpen === true) {
if (!$rootScope.$$phase) {
scope.$apply(function() {
scope.isOpen = false;
});
}
}
$popup.remove();
element.unbind('keydown', inputKeydownBind);
$document.unbind('click', documentClickBind);
});
};
scope.getText = function(key) {
return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
};
scope.isDisabled = function(date) {
if (date === 'today') {
date = new Date();
}
return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) ||
(scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0));
};
scope.compare = function(date1, date2) {
return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
};
// Inner change
scope.dateSelection = function(dt) {
if (angular.isDefined(dt)) {
scope.date = dt;
}
var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
element.val(date);
ngModel.$setViewValue(date);
if (closeOnDateSelection) {
scope.isOpen = false;
element[0].focus();
}
};
scope.keydown = function(evt) {
if (evt.which === 27) {
scope.isOpen = false;
element[0].focus();
}
};
scope.select = function(date) {
if (date === 'today') {
var today = new Date();
if (angular.isDate(scope.date)) {
date = new Date(scope.date);
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
} else {
date = new Date(today.setHours(0, 0, 0, 0));
}
}
scope.dateSelection(date);
};
scope.close = function() {
scope.isOpen = false;
element[0].focus();
};
scope.$watch('isOpen', function(value) {
if (value) {
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
$timeout(function() {
if (onOpenFocus) {
scope.$broadcast('uib:datepicker.focus');
}
$document.bind('click', documentClickBind);
}, 0, false);
} else {
$document.unbind('click', documentClickBind);
}
});
function cameltoDash(string) {
return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
}
function parseDate(viewValue) {
if (angular.isNumber(viewValue)) {
// presumably timestamp to date object
viewValue = new Date(viewValue);
}
if (!viewValue) {
return null;
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
return viewValue;
} else if (angular.isString(viewValue)) {
var date = dateParser.parse(viewValue, dateFormat, scope.date);
if (isNaN(date)) {
return undefined;
} else {
return date;
}
} else {
return undefined;
}
}
function validator(modelValue, viewValue) {
var value = modelValue || viewValue;
if (!attrs.ngRequired && !value) {
return true;
}
if (angular.isNumber(value)) {
value = new Date(value);
}
if (!value) {
return true;
} else if (angular.isDate(value) && !isNaN(value)) {
return true;
} else if (angular.isString(value)) {
var date = dateParser.parse(value, dateFormat);
return !isNaN(date);
} else {
return false;
}
}
function documentClickBind(event) {
var popup = $popup[0];
var dpContainsTarget = element[0].contains(event.target);
// The popup node may not be an element node
// In some browsers (IE) only element nodes have the 'contains' function
var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
scope.$apply(function() {
scope.isOpen = false;
});
}
}
function inputKeydownBind(evt) {
if (evt.which === 27 && scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
scope.$apply(function() {
scope.isOpen = false;
});
element[0].focus();
} else if (evt.which === 40 && !scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
scope.$apply(function() {
scope.isOpen = true;
});
}
}
}])
.directive('uibDatepickerPopup', function() {
return {
require: ['ngModel', 'uibDatepickerPopup'],
controller: 'UibDatepickerPopupController',
scope: {
isOpen: '=?',
currentText: '@',
clearText: '@',
closeText: '@',
dateDisabled: '&',
customClass: '&'
},
link: function(scope, element, attrs, ctrls) {
var ngModel = ctrls[0],
ctrl = ctrls[1];
ctrl.init(ngModel);
}
};
})
.directive('uibDatepickerPopupWrap', function() {
return {
replace: true,
transclude: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/popup.html';
}
};
});
/* Deprecated datepicker below */
angular.module('ui.bootstrap.datepicker')
.value('$datepickerSuppressWarning', false)
.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', '$datepickerSuppressWarning', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, $datepickerSuppressWarning) {
if (!$datepickerSuppressWarning) {
$log.warn('DatepickerController is now deprecated. Use UibDatepickerController instead.');
}
var self = this,
ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
this.modes = ['day', 'month', 'year'];
angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
});
angular.forEach(['minDate', 'maxDate'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($parse($attrs[key]), function(value) {
self[key] = value ? new Date(value) : null;
self.refreshView();
});
} else {
self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
}
});
angular.forEach(['minMode', 'maxMode'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($parse($attrs[key]), function(value) {
self[key] = angular.isDefined(value) ? value : $attrs[key];
$scope[key] = self[key];
if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
$scope.datepickerMode = self[key];
}
});
} else {
self[key] = datepickerConfig[key] || null;
$scope[key] = self[key];
}
});
$scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
if (angular.isDefined($attrs.initDate)) {
this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
$scope.$parent.$watch($attrs.initDate, function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
self.activeDate = initDate;
self.refreshView();
}
});
} else {
this.activeDate = new Date();
}
$scope.isActive = function(dateObject) {
if (self.compare(dateObject.date, self.activeDate) === 0) {
$scope.activeDateId = dateObject.uid;
return true;
}
return false;
};
this.init = function(ngModelCtrl_) {
ngModelCtrl = ngModelCtrl_;
ngModelCtrl.$render = function() {
self.render();
};
};
this.render = function() {
if (ngModelCtrl.$viewValue) {
var date = new Date(ngModelCtrl.$viewValue),
isValid = !isNaN(date);
if (isValid) {
this.activeDate = date;
} else if (!$datepickerSuppressError) {
$log.error('Datepicker 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.');
}
}
this.refreshView();
};
this.refreshView = function() {
if (this.element) {
this._refreshView();
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
}
};
this.createDateObject = function(date, format) {
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
return {
date: date,
label: dateFilter(date, format),
selected: model && this.compare(date, model) === 0,
disabled: this.isDisabled(date),
current: this.compare(date, new Date()) === 0,
customClass: this.customClass(date)
};
};
this.isDisabled = function(date) {
return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
};
this.customClass = function(date) {
return $scope.customClass({date: date, mode: $scope.datepickerMode});
};
// Split array into smaller arrays
this.split = function(arr, size) {
var arrays = [];
while (arr.length > 0) {
arrays.push(arr.splice(0, size));
}
return arrays;
};
this.fixTimeZone = function(date) {
var hours = date.getHours();
date.setHours(hours === 23 ? hours + 2 : 0);
};
$scope.select = function(date) {
if ($scope.datepickerMode === self.minMode) {
var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
ngModelCtrl.$setViewValue(dt);
ngModelCtrl.$render();
} else {
self.activeDate = date;
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
}
};
$scope.move = function(direction) {
var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
month = self.activeDate.getMonth() + direction * (self.step.months || 0);
self.activeDate.setFullYear(year, month, 1);
self.refreshView();
};
$scope.toggleMode = function(direction) {
direction = direction || 1;
if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
return;
}
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
};
// Key event mapper
$scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
var focusElement = function() {
self.element[0].focus();
};
$scope.$on('uib:datepicker.focus', focusElement);
$scope.keydown = function(evt) {
var key = $scope.keys[evt.which];
if (!key || evt.shiftKey || evt.altKey) {
return;
}
evt.preventDefault();
if (!self.shortcutPropagation) {
evt.stopPropagation();
}
if (key === 'enter' || key === 'space') {
if (self.isDisabled(self.activeDate)) {
return; // do nothing
}
$scope.select(self.activeDate);
} else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
$scope.toggleMode(key === 'up' ? 1 : -1);
} else {
self.handleKeyDown(key, evt);
self.refreshView();
}
};
}])
.directive('datepicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/datepicker.html';
},
scope: {
datepickerMode: '=?',
dateDisabled: '&',
customClass: '&',
shortcutPropagation: '&?'
},
require: ['datepicker', '^ngModel'],
controller: 'DatepickerController',
controllerAs: 'datepicker',
link: function(scope, element, attrs, ctrls) {
if (!$datepickerSuppressWarning) {
$log.warn('datepicker is now deprecated. Use uib-datepicker instead.');
}
var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
datepickerCtrl.init(ngModelCtrl);
}
};
}])
.directive('daypicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
replace: true,
templateUrl: 'template/datepicker/day.html',
require: ['^datepicker', 'daypicker'],
controller: 'UibDaypickerController',
link: function(scope, element, attrs, ctrls) {
if (!$datepickerSuppressWarning) {
$log.warn('daypicker is now deprecated. Use uib-daypicker instead.');
}
var datepickerCtrl = ctrls[0],
daypickerCtrl = ctrls[1];
daypickerCtrl.init(datepickerCtrl);
}
};
}])
.directive('monthpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
replace: true,
templateUrl: 'template/datepicker/month.html',
require: ['^datepicker', 'monthpicker'],
controller: 'UibMonthpickerController',
link: function(scope, element, attrs, ctrls) {
if (!$datepickerSuppressWarning) {
$log.warn('monthpicker is now deprecated. Use uib-monthpicker instead.');
}
var datepickerCtrl = ctrls[0],
monthpickerCtrl = ctrls[1];
monthpickerCtrl.init(datepickerCtrl);
}
};
}])
.directive('yearpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
replace: true,
templateUrl: 'template/datepicker/year.html',
require: ['^datepicker', 'yearpicker'],
controller: 'UibYearpickerController',
link: function(scope, element, attrs, ctrls) {
if (!$datepickerSuppressWarning) {
$log.warn('yearpicker is now deprecated. Use uib-yearpicker instead.');
}
var ctrl = ctrls[0];
angular.extend(ctrl, ctrls[1]);
ctrl.yearpickerInit();
ctrl.refreshView();
}
};
}])
.directive('datepickerPopup', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
require: ['ngModel', 'datepickerPopup'],
controller: 'UibDatepickerPopupController',
scope: {
isOpen: '=?',
currentText: '@',
clearText: '@',
closeText: '@',
dateDisabled: '&',
customClass: '&'
},
link: function(scope, element, attrs, ctrls) {
if (!$datepickerSuppressWarning) {
$log.warn('datepicker-popup is now deprecated. Use uib-datepicker-popup instead.');
}
var ngModel = ctrls[0],
ctrl = ctrls[1];
ctrl.init(ngModel);
}
};
}])
.directive('datepickerPopupWrap', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
return {
replace: true,
transclude: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/datepicker/popup.html';
},
link: function() {
if (!$datepickerSuppressWarning) {
$log.warn('datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.');
}
}
};
}]);