| 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.'); |
| } |
| } |
| }; |
| }]); |