diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js
index fdfd63e8c6..2b89878a28 100644
--- a/src/datepicker/datepicker.js
+++ b/src/datepicker/datepicker.js
@@ -1,4 +1,4 @@
-angular.module('ui.bootstrap.datepicker', [])
+angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
.constant('datepickerConfig', {
dayFormat: 'dd',
@@ -9,31 +9,139 @@ angular.module('ui.bootstrap.datepicker', [])
monthTitleFormat: 'yyyy',
showWeeks: true,
startingDay: 0,
- yearRange: 20
+ yearRange: 20,
+ minDate: null,
+ maxDate: null
})
-.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', function (dateFilter, $parse, datepickerConfig) {
+.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
+ var format = {
+ day: getValue($attrs.dayFormat, dtConfig.dayFormat),
+ month: getValue($attrs.monthFormat, dtConfig.monthFormat),
+ year: getValue($attrs.yearFormat, dtConfig.yearFormat),
+ dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
+ dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
+ monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
+ },
+ startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
+ yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
+
+ this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
+ this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
+
+ function getValue(value, defaultValue) {
+ return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
+ }
+
+ function getDaysInMonth( year, month ) {
+ return new Date(year, month, 0).getDate();
+ }
+
+ function getDates(startDate, n) {
+ var dates = new Array(n);
+ var current = startDate, i = 0;
+ while (i < n) {
+ dates[i++] = new Date(current);
+ current.setDate( current.getDate() + 1 );
+ }
+ return dates;
+ }
+
+ function makeDate(date, format, isSelected, isSecondary) {
+ return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
+ }
+
+ this.modes = [
+ {
+ name: 'day',
+ getVisibleDates: function(date, selected) {
+ var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
+ var difference = startingDay - firstDayOfMonth.getDay(),
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
+ firstDate = new Date(firstDayOfMonth), numDates = 0;
+
+ if ( numDisplayedFromPreviousMonth > 0 ) {
+ firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
+ numDates += numDisplayedFromPreviousMonth; // Previous
+ }
+ numDates += getDaysInMonth(year, month + 1); // Current
+ numDates += (7 - numDates % 7) % 7; // Next
+
+ var days = getDates(firstDate, numDates), labels = new Array(7);
+ for (var i = 0; i < numDates; i ++) {
+ var dt = new Date(days[i]);
+ days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
+ }
+ for (var j = 0; j < 7; j++) {
+ labels[j] = dateFilter(days[j].date, format.dayHeader);
+ }
+ return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
+ },
+ compare: function(date1, date2) {
+ return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
+ },
+ split: 7,
+ step: { months: 1 }
+ },
+ {
+ name: 'month',
+ getVisibleDates: function(date, selected) {
+ var months = new Array(12), year = date.getFullYear();
+ for ( var i = 0; i < 12; i++ ) {
+ var dt = new Date(year, i, 1);
+ months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
+ }
+ return { objects: months, title: dateFilter(date, format.monthTitle) };
+ },
+ compare: function(date1, date2) {
+ return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
+ },
+ split: 3,
+ step: { years: 1 }
+ },
+ {
+ name: 'year',
+ getVisibleDates: function(date, selected) {
+ var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
+ for ( var i = 0; i < yearRange; i++ ) {
+ var dt = new Date(startYear + i, 0, 1);
+ years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
+ }
+ return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
+ },
+ compare: function(date1, date2) {
+ return date1.getFullYear() - date2.getFullYear();
+ },
+ split: 5,
+ step: { years: yearRange }
+ }
+ ];
+
+ this.isDisabled = function(date, mode) {
+ var currentMode = this.modes[mode || 0];
+ return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
+ };
+}])
+
+.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
return {
restrict: 'EA',
replace: true,
+ templateUrl: 'template/datepicker/datepicker.html',
scope: {
- model: '=ngModel',
dateDisabled: '&'
},
- templateUrl: 'template/datepicker/datepicker.html',
- link: function(scope, element, attrs) {
- scope.mode = 'day'; // Initial mode
+ require: ['datepicker', '?^ngModel'],
+ controller: 'DatepickerController',
+ link: function(scope, element, attrs, ctrls) {
+ var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
+
+ if (!ngModel) {
+ return; // do nothing if no ng-model
+ }
// Configuration parameters
- var selected = new Date(), showWeeks, minDate, maxDate, format = {};
- format.day = angular.isDefined(attrs.dayFormat) ? scope.$eval(attrs.dayFormat) : datepickerConfig.dayFormat;
- format.month = angular.isDefined(attrs.monthFormat) ? scope.$eval(attrs.monthFormat) : datepickerConfig.monthFormat;
- format.year = angular.isDefined(attrs.yearFormat) ? scope.$eval(attrs.yearFormat) : datepickerConfig.yearFormat;
- format.dayHeader = angular.isDefined(attrs.dayHeaderFormat) ? scope.$eval(attrs.dayHeaderFormat) : datepickerConfig.dayHeaderFormat;
- format.dayTitle = angular.isDefined(attrs.dayTitleFormat) ? scope.$eval(attrs.dayTitleFormat) : datepickerConfig.dayTitleFormat;
- format.monthTitle = angular.isDefined(attrs.monthTitleFormat) ? scope.$eval(attrs.monthTitleFormat) : datepickerConfig.monthTitleFormat;
- var startingDay = angular.isDefined(attrs.startingDay) ? scope.$eval(attrs.startingDay) : datepickerConfig.startingDay;
- var yearRange = angular.isDefined(attrs.yearRange) ? scope.$eval(attrs.yearRange) : datepickerConfig.yearRange;
+ var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
if (attrs.showWeeks) {
scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
@@ -41,174 +149,282 @@ angular.module('ui.bootstrap.datepicker', [])
updateShowWeekNumbers();
});
} else {
- showWeeks = datepickerConfig.showWeeks;
updateShowWeekNumbers();
}
if (attrs.min) {
scope.$parent.$watch($parse(attrs.min), function(value) {
- minDate = value ? new Date(value) : null;
+ datepickerCtrl.minDate = value ? new Date(value) : null;
refill();
});
}
if (attrs.max) {
scope.$parent.$watch($parse(attrs.max), function(value) {
- maxDate = value ? new Date(value) : null;
+ datepickerCtrl.maxDate = value ? new Date(value) : null;
refill();
});
}
- function updateCalendar (rows, labels, title) {
- scope.rows = rows;
- scope.labels = labels;
- scope.title = title;
+ function updateShowWeekNumbers() {
+ scope.showWeekNumbers = mode === 0 && showWeeks;
}
- // Define whether the week number are visible
- function updateShowWeekNumbers() {
- scope.showWeekNumbers = ( scope.mode === 'day' && showWeeks );
+ // Split array into smaller arrays
+ function split(arr, size) {
+ var arrays = [];
+ while (arr.length > 0) {
+ arrays.push(arr.splice(0, size));
+ }
+ return arrays;
}
- function compare( date1, date2 ) {
- if ( scope.mode === 'year') {
- return date2.getFullYear() - date1.getFullYear();
- } else if ( scope.mode === 'month' ) {
- return new Date( date2.getFullYear(), date2.getMonth() ) - new Date( date1.getFullYear(), date1.getMonth() );
- } else if ( scope.mode === 'day' ) {
- return (new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) - new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) );
+ function refill( updateSelected ) {
+ var date = null, valid = true;
+
+ if ( ngModel.$modelValue ) {
+ date = new Date( ngModel.$modelValue );
+
+ if ( isNaN(date) ) {
+ valid = false;
+ $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.');
+ } else if ( updateSelected ) {
+ selected = date;
+ }
}
+ ngModel.$setValidity('date', valid);
+
+ var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
+ angular.forEach(data.objects, function(obj) {
+ obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
+ });
+
+ ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
+
+ scope.rows = split(data.objects, currentMode.split);
+ scope.labels = data.labels || [];
+ scope.title = data.title;
}
- function isDisabled(date) {
- return ((minDate && compare(date, minDate) > 0) || (maxDate && compare(date, maxDate) < 0) || (scope.dateDisabled && scope.dateDisabled({ date: date, mode: scope.mode })));
+ function setMode(value) {
+ mode = value;
+ updateShowWeekNumbers();
+ refill();
}
- // Split array into smaller arrays
- var split = function(a, size) {
- var arrays = [];
- while (a.length > 0) {
- arrays.push(a.splice(0, size));
+ ngModel.$render = function() {
+ refill( true );
+ };
+
+ scope.select = function( date ) {
+ if ( mode === 0 ) {
+ var dt = new Date( ngModel.$modelValue );
+ dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
+ ngModel.$setViewValue( dt );
+ refill( true );
+ } else {
+ selected = date;
+ setMode( mode - 1 );
}
- return arrays;
};
- var getDaysInMonth = function( year, month ) {
- return new Date(year, month + 1, 0).getDate();
+ scope.move = function(direction) {
+ var step = datepickerCtrl.modes[mode].step;
+ selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
+ selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
+ refill();
+ };
+ scope.toggleMode = function() {
+ setMode( (mode + 1) % datepickerCtrl.modes.length );
+ };
+ scope.getWeekNumber = function(row) {
+ return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
};
- var fill = {
- day: function() {
- var days = [], labels = [], lastDate = null;
+ 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;
+ }
+ }
+ };
+}])
- function addDays( dt, n, isCurrentMonth ) {
- for (var i =0; i < n; i ++) {
- days.push( {date: new Date(dt), isCurrent: isCurrentMonth, isSelected: isSelected(dt), label: dateFilter(dt, format.day), disabled: isDisabled(dt) } );
- dt.setDate( dt.getDate() + 1 );
- }
- lastDate = dt;
- }
+.constant('datepickerPopupConfig', {
+ dateFormat: 'yyyy-MM-dd',
+ closeOnDateSelection: true
+})
+
+.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig',
+function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig) {
+ return {
+ restrict: 'EA',
+ require: 'ngModel',
+ link: function(originalScope, element, attrs, ngModel) {
- var d = new Date(selected);
- d.setDate(1);
+ var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
+ var dateFormat = attrs.datepickerPopup || datepickerPopupConfig.dateFormat;
- var difference = startingDay - d.getDay();
- var numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference;
+ // create a child scope for the datepicker directive so we are not polluting original scope
+ var scope = originalScope.$new();
+ originalScope.$on('$destroy', function() {
+ scope.$destroy();
+ });
- if ( numDisplayedFromPreviousMonth > 0 ) {
- d.setDate( - numDisplayedFromPreviousMonth + 1 );
- addDays(d, numDisplayedFromPreviousMonth, false);
- }
- addDays(lastDate || d, getDaysInMonth(selected.getFullYear(), selected.getMonth()), true);
- addDays(lastDate, (7 - days.length % 7) % 7, false);
+ function formatDate(value) {
+ return (value) ? dateFilter(value, dateFormat) : null;
+ }
+ ngModel.$formatters.push(formatDate);
- // Day labels
- for (i = 0; i < 7; i++) {
- labels.push( dateFilter(days[i].date, format.dayHeader) );
- }
- updateCalendar( split( days, 7 ), labels, dateFilter(selected, format.dayTitle) );
- },
- month: function() {
- var months = [], i = 0, year = selected.getFullYear();
- while ( i < 12 ) {
- var dt = new Date(year, i++, 1);
- months.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.month), disabled: isDisabled(dt)} );
- }
- updateCalendar( split( months, 3 ), [], dateFilter(selected, format.monthTitle) );
- },
- year: function() {
- var years = [], year = parseInt((selected.getFullYear() - 1) / yearRange, 10) * yearRange + 1;
- for ( var i = 0; i < yearRange; i++ ) {
- var dt = new Date(year + i, 0, 1);
- years.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.year), disabled: isDisabled(dt)} );
+ // TODO: reverse from dateFilter string to Date object
+ function parseDate(value) {
+ if ( value ) {
+ var date = new Date(value);
+ if (!isNaN(date)) {
+ return date;
}
- var title = years[0].label + ' - ' + years[years.length - 1].label;
- updateCalendar( split( years, 5 ), [], title );
+ }
+ return value;
+ }
+ ngModel.$parsers.push(parseDate);
+
+ var getIsOpen, setIsOpen;
+ if ( attrs.open ) {
+ getIsOpen = $parse(attrs.open);
+ setIsOpen = getIsOpen.assign;
+
+ originalScope.$watch(getIsOpen, function updateOpen(value) {
+ scope.isOpen = !! value;
+ });
+ }
+ scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
+
+ function setOpen( value ) {
+ if (setIsOpen) {
+ setIsOpen(originalScope, !!value);
+ } else {
+ scope.isOpen = !!value;
+ }
+ }
+
+ var documentClickBind = function(event) {
+ if (scope.isOpen && event.target !== element[0]) {
+ scope.$apply(function() {
+ setOpen(false);
+ });
}
};
- var refill = function() {
- fill[scope.mode]();
+
+ var elementFocusBind = function() {
+ scope.$apply(function() {
+ setOpen( true );
+ });
};
- var isSelected = function( dt ) {
- if ( scope.model && scope.model.getFullYear() === dt.getFullYear() ) {
- if ( scope.mode === 'year' ) {
- return true;
- }
- if ( scope.model.getMonth() === dt.getMonth() ) {
- return ( scope.mode === 'month' || (scope.mode === 'day' && scope.model.getDate() === dt.getDate()) );
- }
+
+ // popup element used to display calendar
+ var popupEl = angular.element('');
+ popupEl.attr({
+ 'ng-model': 'date',
+ 'ng-change': 'dateSelection()'
+ });
+ var datepickerEl = popupEl.find('datepicker');
+ if (attrs.datepickerOptions) {
+ datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
+ }
+
+ var $setModelValue = $parse(attrs.ngModel).assign;
+
+ // Inner change
+ scope.dateSelection = function() {
+ $setModelValue(originalScope, scope.date);
+ if (closeOnDateSelection) {
+ setOpen( false );
}
- return false;
};
- scope.$watch('model', function ( dt, olddt ) {
- if ( angular.isDate(dt) ) {
- selected = new Date(dt);
- }
+ // Outter change
+ scope.$watch(function() {
+ return ngModel.$modelValue;
+ }, function(value) {
+ if (angular.isString(value)) {
+ var date = parseDate(value);
- if ( ! angular.equals(dt, olddt) ) {
- refill();
+ if (value && !date) {
+ $setModelValue(originalScope, null);
+ throw new Error(value + ' cannot be parsed to a date object.');
+ } else {
+ value = date;
+ }
}
+ scope.date = value;
+ updatePosition();
});
- scope.$watch('mode', function() {
- updateShowWeekNumbers();
- refill();
- });
- scope.select = function( dt ) {
- selected = new Date(dt);
+ function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
+ if (attribute) {
+ originalScope.$watch($parse(attribute), function(value){
+ scope[scopeProperty] = value;
+ });
+ datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
+ }
+ }
+ addWatchableAttribute(attrs.min, 'min');
+ addWatchableAttribute(attrs.max, 'max');
+ if (attrs.showWeeks) {
+ addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
+ } else {
+ scope.showWeeks = true;
+ datepickerEl.attr('show-weeks', 'showWeeks');
+ }
+ if (attrs.dateDisabled) {
+ datepickerEl.attr('date-disabled', attrs.dateDisabled);
+ }
+
+ function updatePosition() {
+ scope.position = $position.position(element);
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
+ }
- if ( scope.mode === 'year' ) {
- scope.mode = 'month';
- selected.setFullYear( dt.getFullYear() );
- } else if ( scope.mode === 'month' ) {
- scope.mode = 'day';
- selected.setMonth( dt.getMonth() );
- } else if ( scope.mode === 'day' ) {
- scope.model = new Date(selected);
+ scope.$watch('isOpen', function(value) {
+ if (value) {
+ updatePosition();
+ $document.bind('click', documentClickBind);
+ element.unbind('focus', elementFocusBind);
+ element.focus();
+ } else {
+ $document.unbind('click', documentClickBind);
+ element.bind('focus', elementFocusBind);
}
- };
- scope.move = function(step) {
- if (scope.mode === 'day') {
- selected.setMonth( selected.getMonth() + step );
- } else if (scope.mode === 'month') {
- selected.setFullYear( selected.getFullYear() + step );
- } else if (scope.mode === 'year') {
- selected.setFullYear( selected.getFullYear() + step * yearRange );
+
+ if ( setIsOpen ) {
+ setIsOpen(originalScope, value);
}
- refill();
+ });
+
+ scope.today = function() {
+ $setModelValue(originalScope, new Date());
};
- scope.toggleMode = function() {
- scope.mode = ( scope.mode === 'day' ) ? 'month' : ( scope.mode === 'month' ) ? 'year' : 'day';
+ scope.clear = function() {
+ $setModelValue(originalScope, null);
};
- scope.getWeekNumber = function(row) {
- if ( scope.mode !== 'day' || ! scope.showWeekNumbers || row.length !== 7 ) {
- return;
- }
- var index = ( startingDay > 4 ) ? 11 - startingDay : 4 - startingDay; // Thursday
- var d = new Date( row[ index ].date );
- d.setHours(0, 0, 0);
- return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 86400000) + 1) / 7); // 86400000 = 1000*60*60*24;
- };
+ element.after($compile(popupEl)(scope));
+ }
+ };
+}])
+
+.directive('datepickerPopupWrap', [function() {
+ return {
+ restrict:'E',
+ replace: true,
+ transclude: true,
+ templateUrl: 'template/datepicker/popup.html',
+ link:function (scope, element, attrs) {
+ element.bind('click', function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ });
}
};
}]);
\ No newline at end of file
diff --git a/src/datepicker/docs/demo.html b/src/datepicker/docs/demo.html
index fed55e9725..526b2ab62b 100644
--- a/src/datepicker/docs/demo.html
+++ b/src/datepicker/docs/demo.html
@@ -1,9 +1,19 @@
-
Selected date is: {{dt | date:'fullDate' }}
+
+
+
+
+
+
+
+
+
+
-
+
+
-
+
\ No newline at end of file
diff --git a/src/datepicker/docs/demo.js b/src/datepicker/docs/demo.js
index cf7a4df191..18eb51ec83 100644
--- a/src/datepicker/docs/demo.js
+++ b/src/datepicker/docs/demo.js
@@ -1,4 +1,4 @@
-var DatepickerDemoCtrl = function ($scope) {
+var DatepickerDemoCtrl = function ($scope, $timeout) {
$scope.today = function() {
$scope.dt = new Date();
};
@@ -22,4 +22,15 @@ var DatepickerDemoCtrl = function ($scope) {
$scope.minDate = ( $scope.minDate ) ? null : new Date();
};
$scope.toggleMin();
+
+ $scope.open = function() {
+ $timeout(function() {
+ $scope.opened = true;
+ });
+ };
+
+ $scope.dateOptions = {
+ 'year-format': "'yy'",
+ 'starting-day': 1
+ };
};
diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js
index 7e1a77f680..79ae2c0e10 100644
--- a/src/datepicker/test/datepicker.spec.js
+++ b/src/datepicker/test/datepicker.spec.js
@@ -2,11 +2,12 @@ describe('datepicker directive', function () {
var $rootScope, element;
beforeEach(module('ui.bootstrap.datepicker'));
beforeEach(module('template/datepicker/datepicker.html'));
+ beforeEach(module('template/datepicker/popup.html'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.date = new Date("September 30, 2010 15:30:00");
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
}));
@@ -59,19 +60,54 @@ describe('datepicker directive', function () {
return weeks;
}
- function getOptions(rowIndex) {
- var cols = element.find('tbody').find('tr').eq(rowIndex).find('td');
- var days = [];
- for (var i = 1, n = cols.length; i < n; i++) {
- days.push( cols.eq(i).find('button').text() );
+ function getOptions() {
+ var tr = element.find('tbody').find('tr');
+ var rows = [];
+
+ for (var j = 0, numRows = tr.length; j < numRows; j++) {
+ var cols = tr.eq(j).find('td'), days = [];
+ for (var i = 1, n = cols.length; i < n; i++) {
+ days.push( cols.eq(i).find('button').text() );
+ }
+ rows.push(days);
}
- return days;
+ return rows;
}
- function getOptionsEl(rowIndex, colIndex) {
+ function _getOptionEl(rowIndex, colIndex) {
return element.find('tbody').find('tr').eq(rowIndex).find('td').eq(colIndex + 1);
}
+ function clickOption(rowIndex, colIndex) {
+ _getOptionEl(rowIndex, colIndex).find('button').click();
+ }
+
+ function isDisabledOption(rowIndex, colIndex) {
+ return _getOptionEl(rowIndex, colIndex).find('button').prop('disabled');
+ }
+
+ function getAllOptionsEl() {
+ var tr = element.find('tbody').find('tr'), rows = [];
+ for (var i = 0; i < tr.length; i++) {
+ var td = tr.eq(i).find('td'), cols = [];
+ for (var j = 0; j < td.length; j++) {
+ cols.push( td.eq(j + 1) );
+ }
+ rows.push(cols);
+ }
+ return rows;
+ }
+
+ function expectSelectedElement( row, col ) {
+ var options = getAllOptionsEl();
+ for (var i = 0, n = options.length; i < n; i ++) {
+ var optionsRow = options[i];
+ for (var j = 0; j < optionsRow.length; j ++) {
+ expect(optionsRow[j].find('button').hasClass('btn-info')).toBe( i === row && j === col );
+ }
+ }
+ }
+
it('is a `` element', function() {
expect(element.prop('tagName')).toBe('TABLE');
expect(element.find('thead').find('tr').length).toBe(2);
@@ -87,15 +123,17 @@ describe('datepicker directive', function () {
});
it('renders the calendar days correctly', function() {
- expect(getOptions(0)).toEqual(['29', '30', '31', '01', '02', '03', '04']);
- expect(getOptions(1)).toEqual(['05', '06', '07', '08', '09', '10', '11']);
- expect(getOptions(2)).toEqual(['12', '13', '14', '15', '16', '17', '18']);
- expect(getOptions(3)).toEqual(['19', '20', '21', '22', '23', '24', '25']);
- expect(getOptions(4)).toEqual(['26', '27', '28', '29', '30', '01', '02']);
+ expect(getOptions()).toEqual([
+ ['29', '30', '31', '01', '02', '03', '04'],
+ ['05', '06', '07', '08', '09', '10', '11'],
+ ['12', '13', '14', '15', '16', '17', '18'],
+ ['19', '20', '21', '22', '23', '24', '25'],
+ ['26', '27', '28', '29', '30', '01', '02']
+ ]);
});
- it('renders the week numbers correctly', function() {
- expect(getWeeks()).toEqual(['35', '36', '37', '38', '39']);
+ it('renders the week numbers based on ISO 8601', function() {
+ expect(getWeeks()).toEqual(['34', '35', '36', '37', '38']);
});
it('value is correct', function() {
@@ -103,37 +141,36 @@ describe('datepicker directive', function () {
});
it('has `selected` only the correct day', function() {
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( ( i === 4 && j === 4) );
- }
- }
+ expectSelectedElement( 4, 4 );
});
- it('has no `selected` day when model is nulled', function() {
+ it('has no `selected` day when model is cleared', function() {
$rootScope.date = null;
$rootScope.$digest();
expect($rootScope.date).toBe(null);
+ expectSelectedElement( null, null );
+ });
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ it('does not change current view when model is cleared', function() {
+ $rootScope.date = null;
+ $rootScope.$digest();
+
+ expect($rootScope.date).toBe(null);
+ expect(getTitle()).toBe('September 2010');
});
it('`disables` visible dates from other months', function() {
+ var options = getAllOptionsEl();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').find('span').hasClass('muted')).toBe( ((i === 0 && j < 3) || (i === 4 && j > 4)) );
+ expect(options[i][j].find('button').find('span').hasClass('muted')).toBe( ((i === 0 && j < 3) || (i === 4 && j > 4)) );
}
}
});
it('updates the model when a day is clicked', function() {
- var el = getOptionsEl(2, 3).find('button');
- el.click();
+ clickOption(2, 3);
expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
});
@@ -142,24 +179,22 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('August 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
- expect(getOptions(0)).toEqual(['01', '02', '03', '04', '05', '06', '07']);
- expect(getOptions(1)).toEqual(['08', '09', '10', '11', '12', '13', '14']);
- expect(getOptions(2)).toEqual(['15', '16', '17', '18', '19', '20', '21']);
- expect(getOptions(3)).toEqual(['22', '23', '24', '25', '26', '27', '28']);
- expect(getOptions(4)).toEqual(['29', '30', '31', '01', '02', '03', '04']);
-
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ expect(getOptions()).toEqual([
+ ['01', '02', '03', '04', '05', '06', '07'],
+ ['08', '09', '10', '11', '12', '13', '14'],
+ ['15', '16', '17', '18', '19', '20', '21'],
+ ['22', '23', '24', '25', '26', '27', '28'],
+ ['29', '30', '31', '01', '02', '03', '04']
+ ]);
+
+ expectSelectedElement( null, null );
});
it('updates the model only when when a day is clicked in the `previous` month', function() {
clickPreviousButton();
expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
- getOptionsEl(2, 3).find('button').click();
+ clickOption(2, 3);
expect($rootScope.date).toEqual(new Date('August 18, 2010 15:30:00'));
});
@@ -168,61 +203,90 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('October 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
- expect(getOptions(0)).toEqual(['26', '27', '28', '29', '30', '01', '02']);
- expect(getOptions(1)).toEqual(['03', '04', '05', '06', '07', '08', '09']);
- expect(getOptions(2)).toEqual(['10', '11', '12', '13', '14', '15', '16']);
- expect(getOptions(3)).toEqual(['17', '18', '19', '20', '21', '22', '23']);
- expect(getOptions(4)).toEqual(['24', '25', '26', '27', '28', '29', '30']);
-
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( (i === 0 && j === 4) );
- }
- }
+ expect(getOptions()).toEqual([
+ ['26', '27', '28', '29', '30', '01', '02'],
+ ['03', '04', '05', '06', '07', '08', '09'],
+ ['10', '11', '12', '13', '14', '15', '16'],
+ ['17', '18', '19', '20', '21', '22', '23'],
+ ['24', '25', '26', '27', '28', '29', '30'],
+ ['31', '01', '02', '03', '04', '05', '06']
+ ]);
+
+ expectSelectedElement( 0, 4 );
});
it('updates the model only when when a day is clicked in the `next` month', function() {
clickNextButton();
expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
- getOptionsEl(2, 3).find('button').click();
+ clickOption(2, 3);
expect($rootScope.date).toEqual(new Date('October 13, 2010 15:30:00'));
});
it('updates the calendar when a day of another month is selected', function() {
- getOptionsEl(4, 5).find('button').click();
+ clickOption(4, 5);
expect($rootScope.date).toEqual(new Date('October 01, 2010 15:30:00'));
expect(getTitle()).toBe('October 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
- expect(getOptions(0)).toEqual(['26', '27', '28', '29', '30', '01', '02']);
- expect(getOptions(1)).toEqual(['03', '04', '05', '06', '07', '08', '09']);
- expect(getOptions(2)).toEqual(['10', '11', '12', '13', '14', '15', '16']);
- expect(getOptions(3)).toEqual(['17', '18', '19', '20', '21', '22', '23']);
- expect(getOptions(4)).toEqual(['24', '25', '26', '27', '28', '29', '30']);
-
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( (i === 0 && j === 5) );
- }
- }
+ expect(getOptions()).toEqual([
+ ['26', '27', '28', '29', '30', '01', '02'],
+ ['03', '04', '05', '06', '07', '08', '09'],
+ ['10', '11', '12', '13', '14', '15', '16'],
+ ['17', '18', '19', '20', '21', '22', '23'],
+ ['24', '25', '26', '27', '28', '29', '30'],
+ ['31', '01', '02', '03', '04', '05', '06']
+ ]);
+
+ expectSelectedElement( 0, 5 );
});
- it('updates calendar when `model` changes', function() {
- $rootScope.date = new Date('November 7, 2005 23:30:00');
- $rootScope.$digest();
+ describe('when `model` changes', function () {
+ function testCalendar() {
+ expect(getTitle()).toBe('November 2005');
+ expect(getOptions()).toEqual([
+ ['30', '31', '01', '02', '03', '04', '05'],
+ ['06', '07', '08', '09', '10', '11', '12'],
+ ['13', '14', '15', '16', '17', '18', '19'],
+ ['20', '21', '22', '23', '24', '25', '26'],
+ ['27', '28', '29', '30', '01', '02', '03']
+ ]);
+
+ expectSelectedElement( 1, 1 );
+ }
- expect(getTitle()).toBe('November 2005');
- expect(getOptions(0)).toEqual(['30', '31', '01', '02', '03', '04', '05']);
- expect(getOptions(1)).toEqual(['06', '07', '08', '09', '10', '11', '12']);
- expect(getOptions(2)).toEqual(['13', '14', '15', '16', '17', '18', '19']);
- expect(getOptions(3)).toEqual(['20', '21', '22', '23', '24', '25', '26']);
- expect(getOptions(4)).toEqual(['27', '28', '29', '30', '01', '02', '03']);
+ describe('to a Date object', function() {
+ it('updates', function() {
+ $rootScope.date = new Date('November 7, 2005 23:30:00');
+ $rootScope.$digest();
+ testCalendar();
+ expect(angular.isDate($rootScope.date)).toBe(true);
+ });
+ });
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( (i === 1 && j === 1) );
- }
- }
+ describe('not to a Date object', function() {
+
+ it('to a Number, it updates calendar', function() {
+ $rootScope.date = parseInt((new Date('November 7, 2005 23:30:00')).getTime(), 10);
+ $rootScope.$digest();
+ testCalendar();
+ expect(angular.isNumber($rootScope.date)).toBe(true);
+ });
+
+ it('to a string that can be parsed by Date, it updates calendar', function() {
+ $rootScope.date = 'November 7, 2005 23:30:00';
+ $rootScope.$digest();
+ testCalendar();
+ expect(angular.isString($rootScope.date)).toBe(true);
+ });
+
+ it('to a string that cannot be parsed by Date, it gets invalid', function() {
+ $rootScope.date = 'pizza';
+ $rootScope.$digest();
+ expect(element.hasClass('ng-invalid')).toBeTruthy();
+ expect(element.hasClass('ng-invalid-date')).toBeTruthy();
+ expect($rootScope.date).toBe('pizza');
+ });
+ });
});
it('loops between different modes', function() {
@@ -249,10 +313,12 @@ describe('datepicker directive', function () {
it('shows months as options', function() {
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['January', 'February', 'March']);
- expect(getOptions(1)).toEqual(['April', 'May', 'June']);
- expect(getOptions(2)).toEqual(['July', 'August', 'September']);
- expect(getOptions(3)).toEqual(['October', 'November', 'December']);
+ expect(getOptions()).toEqual([
+ ['January', 'February', 'March'],
+ ['April', 'May', 'June'],
+ ['July', 'August', 'September'],
+ ['October', 'November', 'December']
+ ]);
});
it('does not change the model', function() {
@@ -260,11 +326,7 @@ describe('datepicker directive', function () {
});
it('has `selected` only the correct month', function() {
- for (var i = 0; i < 4; i ++) {
- for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( ( i === 2 && j === 2) );
- }
- }
+ expectSelectedElement( 2, 2 );
});
it('moves to the previous year when `previous` button is clicked', function() {
@@ -272,16 +334,14 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('2009');
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['January', 'February', 'March']);
- expect(getOptions(1)).toEqual(['April', 'May', 'June']);
- expect(getOptions(2)).toEqual(['July', 'August', 'September']);
- expect(getOptions(3)).toEqual(['October', 'November', 'December']);
-
- for (var i = 0; i < 5; i ++) {
- for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ expect(getOptions()).toEqual([
+ ['January', 'February', 'March'],
+ ['April', 'May', 'June'],
+ ['July', 'August', 'September'],
+ ['October', 'November', 'December']
+ ]);
+
+ expectSelectedElement( null, null );
});
it('moves to the next year when `next` button is clicked', function() {
@@ -289,31 +349,33 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('2011');
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['January', 'February', 'March']);
- expect(getOptions(1)).toEqual(['April', 'May', 'June']);
- expect(getOptions(2)).toEqual(['July', 'August', 'September']);
- expect(getOptions(3)).toEqual(['October', 'November', 'December']);
-
- for (var i = 0; i < 4; i ++) {
- for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ expect(getOptions()).toEqual([
+ ['January', 'February', 'March'],
+ ['April', 'May', 'June'],
+ ['July', 'August', 'September'],
+ ['October', 'November', 'December']
+ ]);
+
+ expectSelectedElement( null, null );
});
it('renders correctly when a month is clicked', function() {
clickPreviousButton(5);
expect(getTitle()).toBe('2005');
- var monthNovEl = getOptionsEl(3, 1).find('button');
- monthNovEl.click();
+ clickOption(3, 1);
expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
expect(getTitle()).toBe('November 2005');
- expect(getOptions(0)).toEqual(['30', '31', '01', '02', '03', '04', '05']);
- expect(getOptions(1)).toEqual(['06', '07', '08', '09', '10', '11', '12']);
- expect(getOptions(2)).toEqual(['13', '14', '15', '16', '17', '18', '19']);
- expect(getOptions(3)).toEqual(['20', '21', '22', '23', '24', '25', '26']);
- expect(getOptions(4)).toEqual(['27', '28', '29', '30', '01', '02', '03']);
+ expect(getOptions()).toEqual([
+ ['30', '31', '01', '02', '03', '04', '05'],
+ ['06', '07', '08', '09', '10', '11', '12'],
+ ['13', '14', '15', '16', '17', '18', '19'],
+ ['20', '21', '22', '23', '24', '25', '26'],
+ ['27', '28', '29', '30', '01', '02', '03']
+ ]);
+
+ clickOption(2, 3);
+ expect($rootScope.date).toEqual(new Date('November 16, 2005 15:30:00'));
});
});
@@ -328,10 +390,12 @@ describe('datepicker directive', function () {
it('shows years as options', function() {
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['2001', '2002', '2003', '2004', '2005']);
- expect(getOptions(1)).toEqual(['2006', '2007', '2008', '2009', '2010']);
- expect(getOptions(2)).toEqual(['2011', '2012', '2013', '2014', '2015']);
- expect(getOptions(3)).toEqual(['2016', '2017', '2018', '2019', '2020']);
+ expect(getOptions()).toEqual([
+ ['2001', '2002', '2003', '2004', '2005'],
+ ['2006', '2007', '2008', '2009', '2010'],
+ ['2011', '2012', '2013', '2014', '2015'],
+ ['2016', '2017', '2018', '2019', '2020']
+ ]);
});
it('does not change the model', function() {
@@ -339,11 +403,7 @@ describe('datepicker directive', function () {
});
it('has `selected` only the selected year', function() {
- for (var i = 0; i < 4; i ++) {
- for (var j = 0; j < 5; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( ( i === 1 && j === 4) );
- }
- }
+ expectSelectedElement( 1, 4 );
});
it('moves to the previous year set when `previous` button is clicked', function() {
@@ -351,16 +411,13 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('1981 - 2000');
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['1981', '1982', '1983', '1984', '1985']);
- expect(getOptions(1)).toEqual(['1986', '1987', '1988', '1989', '1990']);
- expect(getOptions(2)).toEqual(['1991', '1992', '1993', '1994', '1995']);
- expect(getOptions(3)).toEqual(['1996', '1997', '1998', '1999', '2000']);
-
- for (var i = 0; i < 4; i ++) {
- for (var j = 0; j < 5; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ expect(getOptions()).toEqual([
+ ['1981', '1982', '1983', '1984', '1985'],
+ ['1986', '1987', '1988', '1989', '1990'],
+ ['1991', '1992', '1993', '1994', '1995'],
+ ['1996', '1997', '1998', '1999', '2000']
+ ]);
+ expectSelectedElement( null, null );
});
it('moves to the next year set when `next` button is clicked', function() {
@@ -368,22 +425,21 @@ describe('datepicker directive', function () {
expect(getTitle()).toBe('2021 - 2040');
expect(getLabels()).toEqual([]);
- expect(getOptions(0)).toEqual(['2021', '2022', '2023', '2024', '2025']);
- expect(getOptions(1)).toEqual(['2026', '2027', '2028', '2029', '2030']);
- expect(getOptions(2)).toEqual(['2031', '2032', '2033', '2034', '2035']);
- expect(getOptions(3)).toEqual(['2036', '2037', '2038', '2039', '2040']);
-
- for (var i = 0; i < 4; i ++) {
- for (var j = 0; j < 5; j ++) {
- expect(getOptionsEl(i, j).find('button').hasClass('btn-info')).toBe( false );
- }
- }
+ expect(getOptions()).toEqual([
+ ['2021', '2022', '2023', '2024', '2025'],
+ ['2026', '2027', '2028', '2029', '2030'],
+ ['2031', '2032', '2033', '2034', '2035'],
+ ['2036', '2037', '2038', '2039', '2040']
+ ]);
+
+ expectSelectedElement( null, null );
});
});
describe('attribute `starting-day`', function () {
beforeEach(function() {
- element = $compile('')($rootScope);
+ $rootScope.startingDay = 1;
+ element = $compile('')($rootScope);
$rootScope.$digest();
});
@@ -392,11 +448,13 @@ describe('datepicker directive', function () {
});
it('renders the calendar days correctly', function() {
- expect(getOptions(0)).toEqual(['30', '31', '01', '02', '03', '04', '05']);
- expect(getOptions(1)).toEqual(['06', '07', '08', '09', '10', '11', '12']);
- expect(getOptions(2)).toEqual(['13', '14', '15', '16', '17', '18', '19']);
- expect(getOptions(3)).toEqual(['20', '21', '22', '23', '24', '25', '26']);
- expect(getOptions(4)).toEqual(['27', '28', '29', '30', '01', '02', '03']);
+ expect(getOptions()).toEqual([
+ ['30', '31', '01', '02', '03', '04', '05'],
+ ['06', '07', '08', '09', '10', '11', '12'],
+ ['13', '14', '15', '16', '17', '18', '19'],
+ ['20', '21', '22', '23', '24', '25', '26'],
+ ['27', '28', '29', '30', '01', '02', '03']
+ ]);
});
it('renders the week numbers correctly', function() {
@@ -408,7 +466,7 @@ describe('datepicker directive', function () {
var weekHeader, weekElement;
beforeEach(function() {
$rootScope.showWeeks = false;
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
weekHeader = getLabelsRow().find('th').eq(0);
@@ -439,14 +497,14 @@ describe('datepicker directive', function () {
describe('min attribute', function () {
beforeEach(function() {
$rootScope.mindate = new Date("September 12, 2010");
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
});
it('disables appropriate days in current month', function() {
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i < 2) );
+ expect(isDisabledOption(i, j)).toBe( (i < 2) );
}
}
});
@@ -456,16 +514,24 @@ describe('datepicker directive', function () {
$rootScope.$digest();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i < 1) );
+ expect(isDisabledOption(i, j)).toBe( (i < 1) );
}
}
});
+ it('invalidates when model is a disabled date', function() {
+ $rootScope.mindate = new Date("September 5, 2010");
+ $rootScope.date = new Date("September 2, 2010");
+ $rootScope.$digest();
+ expect(element.hasClass('ng-invalid')).toBeTruthy();
+ expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();
+ });
+
it('disables all days in previous month', function() {
clickPreviousButton();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( true );
+ expect(isDisabledOption(i, j)).toBe( true );
}
}
});
@@ -474,7 +540,7 @@ describe('datepicker directive', function () {
clickNextButton();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -483,7 +549,7 @@ describe('datepicker directive', function () {
clickTitleButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i < 2 || (i === 2 && j < 2)) );
+ expect(isDisabledOption(i, j)).toBe( (i < 2 || (i === 2 && j < 2)) );
}
}
});
@@ -493,7 +559,7 @@ describe('datepicker directive', function () {
clickPreviousButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( true );
+ expect(isDisabledOption(i, j)).toBe( true );
}
}
});
@@ -503,7 +569,7 @@ describe('datepicker directive', function () {
clickNextButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -516,7 +582,7 @@ describe('datepicker directive', function () {
clickTitleButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -526,14 +592,14 @@ describe('datepicker directive', function () {
describe('max attribute', function () {
beforeEach(function() {
$rootScope.maxdate = new Date("September 25, 2010");
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
});
it('disables appropriate days in current month', function() {
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i === 4) );
+ expect(isDisabledOption(i, j)).toBe( (i === 4) );
}
}
});
@@ -543,16 +609,23 @@ describe('datepicker directive', function () {
$rootScope.$digest();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i > 2) );
+ expect(isDisabledOption(i, j)).toBe( (i > 2) );
}
}
});
+ it('invalidates when model is a disabled date', function() {
+ $rootScope.maxdate = new Date("September 18, 2010");
+ $rootScope.$digest();
+ expect(element.hasClass('ng-invalid')).toBeTruthy();
+ expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();
+ });
+
it('disables no days in previous month', function() {
clickPreviousButton();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -561,7 +634,7 @@ describe('datepicker directive', function () {
clickNextButton();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( true );
+ expect(isDisabledOption(i, j)).toBe( true );
}
}
});
@@ -570,7 +643,7 @@ describe('datepicker directive', function () {
clickTitleButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( (i > 2 || (i === 2 && j > 2)) );
+ expect(isDisabledOption(i, j)).toBe( (i > 2 || (i === 2 && j > 2)) );
}
}
});
@@ -580,7 +653,7 @@ describe('datepicker directive', function () {
clickPreviousButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -590,7 +663,7 @@ describe('datepicker directive', function () {
clickNextButton();
for (var i = 0; i < 4; i ++) {
for (var j = 0; j < 3; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( true );
+ expect(isDisabledOption(i, j)).toBe( true );
}
}
});
@@ -600,7 +673,7 @@ describe('datepicker directive', function () {
$rootScope.$digest();
for (var i = 0; i < 5; i ++) {
for (var j = 0; j < 7; j ++) {
- expect(getOptionsEl(i, j).find('button').prop('disabled')).toBe( false );
+ expect(isDisabledOption(i, j)).toBe( false );
}
}
});
@@ -609,28 +682,31 @@ describe('datepicker directive', function () {
describe('date-disabled expression', function () {
beforeEach(function() {
$rootScope.dateDisabledHandler = jasmine.createSpy('dateDisabledHandler');
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
});
- it('executes the dateDisabled expression for each visible date', function() {
- expect($rootScope.dateDisabledHandler.calls.length).toEqual(35);
+ it('executes the dateDisabled expression for each visible day plus one for validation', function() {
+ expect($rootScope.dateDisabledHandler.calls.length).toEqual(35 + 1);
});
- it('executes the dateDisabled expression for each visible date & each month when mode changes', function() {
+ it('executes the dateDisabled expression for each visible month plus one for validation', function() {
+ $rootScope.dateDisabledHandler.reset();
clickTitleButton();
- expect($rootScope.dateDisabledHandler.calls.length).toEqual(35 + 12);
+ expect($rootScope.dateDisabledHandler.calls.length).toEqual(12 + 1);
});
- it('executes the dateDisabled expression for each visible date, month & year when mode changes', function() {
- clickTitleButton(2);
- expect($rootScope.dateDisabledHandler.calls.length).toEqual(35 + 12 + 20);
+ it('executes the dateDisabled expression for each visible year plus one for validation', function() {
+ clickTitleButton();
+ $rootScope.dateDisabledHandler.reset();
+ clickTitleButton();
+ expect($rootScope.dateDisabledHandler.calls.length).toEqual(20 + 1);
});
});
describe('formatting attributes', function () {
beforeEach(function() {
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
});
@@ -642,18 +718,22 @@ describe('datepicker directive', function () {
clickTitleButton();
expect(getTitle()).toBe('10');
- expect(getOptions(0)).toEqual(['Jan', 'Feb', 'Mar']);
- expect(getOptions(1)).toEqual(['Apr', 'May', 'Jun']);
- expect(getOptions(2)).toEqual(['Jul', 'Aug', 'Sep']);
- expect(getOptions(3)).toEqual(['Oct', 'Nov', 'Dec']);
+ expect(getOptions()).toEqual([
+ ['Jan', 'Feb', 'Mar'],
+ ['Apr', 'May', 'Jun'],
+ ['Jul', 'Aug', 'Sep'],
+ ['Oct', 'Nov', 'Dec']
+ ]);
});
it('changes the title, year format & range in `year` mode', function() {
clickTitleButton(2);
expect(getTitle()).toBe('01 - 10');
- expect(getOptions(0)).toEqual(['01', '02', '03', '04', '05']);
- expect(getOptions(1)).toEqual(['06', '07', '08', '09', '10']);
+ expect(getOptions()).toEqual([
+ ['01', '02', '03', '04', '05'],
+ ['06', '07', '08', '09', '10']
+ ]);
});
it('shows day labels', function() {
@@ -661,15 +741,19 @@ describe('datepicker directive', function () {
});
it('changes the day format', function() {
- expect(getOptions(0)).toEqual(['29', '30', '31', '1', '2', '3', '4']);
- expect(getOptions(1)).toEqual(['5', '6', '7', '8', '9', '10', '11']);
- expect(getOptions(4)).toEqual(['26', '27', '28', '29', '30', '1', '2']);
+ expect(getOptions()).toEqual([
+ ['29', '30', '31', '1', '2', '3', '4'],
+ ['5', '6', '7', '8', '9', '10', '11'],
+ ['12', '13', '14', '15', '16', '17', '18'],
+ ['19', '20', '21', '22', '23', '24', '25'],
+ ['26', '27', '28', '29', '30', '1', '2']
+ ]);
});
});
describe('setting datepickerConfig', function() {
var originalConfig = {};
- beforeEach(inject(function(_$compile_, _$rootScope_, datepickerConfig) {
+ beforeEach(inject(function(datepickerConfig) {
angular.extend(originalConfig, datepickerConfig);
datepickerConfig.startingDay = 6;
datepickerConfig.showWeeks = false;
@@ -681,7 +765,7 @@ describe('datepicker directive', function () {
datepickerConfig.dayTitleFormat = 'MMMM, yy';
datepickerConfig.monthTitleFormat = 'yy';
- element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.$digest();
}));
afterEach(inject(function(datepickerConfig) {
@@ -689,7 +773,7 @@ describe('datepicker directive', function () {
angular.extend(datepickerConfig, originalConfig);
}));
- it('changes the title format in day mode', function() {
+ it('changes the title format in `day` mode', function() {
expect(getTitle()).toBe('September, 10');
});
@@ -697,35 +781,319 @@ describe('datepicker directive', function () {
clickTitleButton();
expect(getTitle()).toBe('10');
- expect(getOptions(0)).toEqual(['Jan', 'Feb', 'Mar']);
- expect(getOptions(1)).toEqual(['Apr', 'May', 'Jun']);
- expect(getOptions(2)).toEqual(['Jul', 'Aug', 'Sep']);
- expect(getOptions(3)).toEqual(['Oct', 'Nov', 'Dec']);
+ expect(getOptions()).toEqual([
+ ['Jan', 'Feb', 'Mar'],
+ ['Apr', 'May', 'Jun'],
+ ['Jul', 'Aug', 'Sep'],
+ ['Oct', 'Nov', 'Dec']
+ ]);
});
it('changes the title, year format & range in `year` mode', function() {
clickTitleButton(2);
expect(getTitle()).toBe('01 - 10');
- expect(getOptions(0)).toEqual(['01', '02', '03', '04', '05']);
- expect(getOptions(1)).toEqual(['06', '07', '08', '09', '10']);
+ expect(getOptions()).toEqual([
+ ['01', '02', '03', '04', '05'],
+ ['06', '07', '08', '09', '10']
+ ]);
});
it('changes the `starting-day` & day headers & format', function() {
expect(getLabels()).toEqual(['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']);
- expect(getOptions(0)).toEqual(['28', '29', '30', '31', '1', '2', '3']);
- expect(getOptions(1)).toEqual(['4', '5', '6', '7', '8', '9', '10']);
- expect(getOptions(4)).toEqual(['25', '26', '27', '28', '29', '30', '1']);
+ expect(getOptions()).toEqual([
+ ['28', '29', '30', '31', '1', '2', '3'],
+ ['4', '5', '6', '7', '8', '9', '10'],
+ ['11', '12', '13', '14', '15', '16', '17'],
+ ['18', '19', '20', '21', '22', '23', '24'],
+ ['25', '26', '27', '28', '29', '30', '1']
+ ]);
});
it('changes initial visibility for weeks', function() {
expect(getLabelsRow().find('th').eq(0).css('display')).toBe('none');
+ var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
- expect(element.find('tbody').find('tr').eq(i).find('td').eq(0).css('display')).toBe('none');
+ expect(tr.eq(i).find('td').eq(0).css('display')).toBe('none');
}
});
});
+
+ describe('controller', function () {
+ var ctrl, $attrs;
+ beforeEach(inject(function($controller) {
+ $rootScope.dateDisabled = null;
+ $attrs = {};
+ ctrl = $controller('DatepickerController', { $scope: $rootScope, $attrs: $attrs });
+ }));
+
+ describe('modes', function() {
+ var currentMode;
+
+ it('to be an array', function() {
+ expect(ctrl.modes.length).toBe(3);
+ });
+
+ describe('`day`', function() {
+ beforeEach(inject(function() {
+ currentMode = ctrl.modes[0];
+ }));
+
+ it('has the appropriate name', function() {
+ expect(currentMode.name).toBe('day');
+ });
+
+ it('returns the correct date objects', function() {
+ var objs = currentMode.getVisibleDates(new Date('September 1, 2010'), new Date('September 30, 2010')).objects;
+ expect(objs.length).toBe(35);
+ expect(objs[1].selected).toBeFalsy();
+ expect(objs[32].selected).toBeTruthy();
+ });
+
+ it('can compare two dates', function() {
+ expect(currentMode.compare(new Date('September 30, 2010'), new Date('September 1, 2010'))).toBeGreaterThan(0);
+ expect(currentMode.compare(new Date('September 1, 2010'), new Date('September 30, 2010'))).toBeLessThan(0);
+ expect(currentMode.compare(new Date('September 30, 2010 15:30:00'), new Date('September 30, 2010 20:30:00'))).toBe(0);
+ });
+ });
+
+ describe('`month`', function() {
+ beforeEach(inject(function() {
+ currentMode = ctrl.modes[1];
+ }));
+
+ it('has the appropriate name', function() {
+ expect(currentMode.name).toBe('month');
+ });
+
+ it('returns the correct date objects', function() {
+ var objs = currentMode.getVisibleDates(new Date('September 1, 2010'), new Date('September 30, 2010')).objects;
+ expect(objs.length).toBe(12);
+ expect(objs[1].selected).toBeFalsy();
+ expect(objs[8].selected).toBeTruthy();
+ });
+
+ it('can compare two dates', function() {
+ expect(currentMode.compare(new Date('October 30, 2010'), new Date('September 01, 2010'))).toBeGreaterThan(0);
+ expect(currentMode.compare(new Date('September 01, 2010'), new Date('October 30, 2010'))).toBeLessThan(0);
+ expect(currentMode.compare(new Date('September 01, 2010'), new Date('September 30, 2010'))).toBe(0);
+ });
+ });
+
+ describe('`year`', function() {
+ beforeEach(inject(function() {
+ currentMode = ctrl.modes[2];
+ }));
+
+ it('has the appropriate name', function() {
+ expect(currentMode.name).toBe('year');
+ });
+
+ it('returns the correct date objects', function() {
+ var objs = currentMode.getVisibleDates(new Date('September 1, 2010'), new Date('September 01, 2010')).objects;
+ expect(objs.length).toBe(20);
+ expect(objs[1].selected).toBeFalsy();
+ expect(objs[9].selected).toBeTruthy();
+ });
+
+ it('can compare two dates', function() {
+ expect(currentMode.compare(new Date('September 1, 2011'), new Date('October 30, 2010'))).toBeGreaterThan(0);
+ expect(currentMode.compare(new Date('October 30, 2010'), new Date('September 1, 2011'))).toBeLessThan(0);
+ expect(currentMode.compare(new Date('November 9, 2010'), new Date('September 30, 2010'))).toBe(0);
+ });
+ });
+ });
+
+ describe('`isDisabled` function', function() {
+ var date = new Date("September 30, 2010 15:30:00");
+
+ it('to return false if no limit is set', function() {
+ expect(ctrl.isDisabled(date, 0)).toBeFalsy();
+ });
+
+ it('to handle correctly the `min` date', function() {
+ ctrl.minDate = new Date('October 1, 2010');
+ expect(ctrl.isDisabled(date, 0)).toBeTruthy();
+ expect(ctrl.isDisabled(date)).toBeTruthy();
+
+ ctrl.minDate = new Date('September 1, 2010');
+ expect(ctrl.isDisabled(date, 0)).toBeFalsy();
+ });
+
+ it('to handle correctly the `max` date', function() {
+ ctrl.maxDate = new Date('October 1, 2010');
+ expect(ctrl.isDisabled(date, 0)).toBeFalsy();
+
+ ctrl.maxDate = new Date('September 1, 2010');
+ expect(ctrl.isDisabled(date, 0)).toBeTruthy();
+ expect(ctrl.isDisabled(date)).toBeTruthy();
+ });
+
+ it('to handle correctly the scope `dateDisabled` expression', function() {
+ $rootScope.dateDisabled = function() {
+ return false;
+ };
+ $rootScope.$digest();
+ expect(ctrl.isDisabled(date, 0)).toBeFalsy();
+
+ $rootScope.dateDisabled = function() {
+ return true;
+ };
+ $rootScope.$digest();
+ expect(ctrl.isDisabled(date, 0)).toBeTruthy();
+ });
+ });
+ });
+
+ describe('as popup', function () {
+ var divElement, inputEl, dropdownEl, changeInputValueTo, $document;
+
+ function assignElements(wrapElement) {
+ inputEl = wrapElement.find('input');
+ dropdownEl = wrapElement.find('ul');
+ element = dropdownEl.find('table');
+ }
+
+ beforeEach(inject(function(_$document_, $sniffer) {
+ $document = _$document_;
+ $rootScope.date = new Date("September 30, 2010 15:30:00");
+ var wrapElement = $compile('')($rootScope);
+ $rootScope.$digest();
+ assignElements(wrapElement);
+
+ changeInputValueTo = function (el, value) {
+ el.val(value);
+ el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+ $rootScope.$digest();
+ };
+ }));
+
+ it('to display the correct value in input', function() {
+ expect(inputEl.val()).toBe('2010-09-30');
+ });
+
+ it('does not to display datepicker initially', function() {
+ expect(dropdownEl.css('display')).toBe('none');
+ });
+
+ it('displays datepicker on input focus', function() {
+ inputEl.focus();
+ expect(dropdownEl.css('display')).not.toBe('none');
+ });
+
+ it('renders the calendar correctly', function() {
+ expect(getLabelsRow().css('display')).not.toBe('none');
+ expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+ expect(getOptions()).toEqual([
+ ['29', '30', '31', '01', '02', '03', '04'],
+ ['05', '06', '07', '08', '09', '10', '11'],
+ ['12', '13', '14', '15', '16', '17', '18'],
+ ['19', '20', '21', '22', '23', '24', '25'],
+ ['26', '27', '28', '29', '30', '01', '02']
+ ]);
+ });
+
+ it('updates the input when a day is clicked', function() {
+ clickOption(2, 3);
+ expect(inputEl.val()).toBe('2010-09-15');
+ expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+ });
+
+ it('updates the input correctly when model changes', function() {
+ $rootScope.date = new Date("January 10, 1983 10:00:00");
+ $rootScope.$digest();
+ expect(inputEl.val()).toBe('1983-01-10');
+ });
+
+ it('closes the dropdown when a day is clicked', function() {
+ inputEl.focus();
+ expect(dropdownEl.css('display')).not.toBe('none');
+
+ clickOption(2, 3);
+ expect(dropdownEl.css('display')).toBe('none');
+ });
+
+ it('updates the model when input value changes', function() {
+ changeInputValueTo(inputEl, 'March 5, 1980');
+ expect($rootScope.date.getFullYear()).toEqual(1980);
+ expect($rootScope.date.getMonth()).toEqual(2);
+ expect($rootScope.date.getDate()).toEqual(5);
+ });
+
+ it('closes when click outside of calendar', function() {
+ $document.find('body').click();
+ expect(dropdownEl.css('display')).toBe('none');
+ });
+
+ describe('toggles programatically by `open` attribute', function () {
+ beforeEach(inject(function() {
+ $rootScope.open = true;
+ var wrapElement = $compile('
')($rootScope);
+ $rootScope.$digest();
+ assignElements(wrapElement);
+ }));
+
+ it('to display initially', function() {
+ expect(dropdownEl.css('display')).not.toBe('none');
+ });
+
+ it('to close / open from scope variable', function() {
+ expect(dropdownEl.css('display')).not.toBe('none');
+ $rootScope.open = false;
+ $rootScope.$digest();
+ expect(dropdownEl.css('display')).toBe('none');
+
+ $rootScope.open = true;
+ $rootScope.$digest();
+ expect(dropdownEl.css('display')).not.toBe('none');
+ });
+ });
+
+ describe('custom format', function () {
+ beforeEach(inject(function() {
+ var wrapElement = $compile('
')($rootScope);
+ $rootScope.$digest();
+ assignElements(wrapElement);
+ }));
+
+ it('to display the correct value in input', function() {
+ expect(inputEl.val()).toBe('30-September-2010');
+ });
+
+ it('updates the input when a day is clicked', function() {
+ clickOption(2, 3);
+ expect(inputEl.val()).toBe('15-September-2010');
+ expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+ });
+
+ it('updates the input correctly when model changes', function() {
+ $rootScope.date = new Date("January 10, 1983 10:00:00");
+ $rootScope.$digest();
+ expect(inputEl.val()).toBe('10-January-1983');
+ });
+ });
+
+ describe('use with ng-required directive', function() {
+ beforeEach(inject(function() {
+ $rootScope.date = '';
+ var wrapElement = $compile('
')($rootScope);
+ $rootScope.$digest();
+ assignElements(wrapElement);
+ }));
+
+ it('should be invalid initially', function() {
+ expect(inputEl.hasClass('ng-invalid')).toBeTruthy();
+ });
+ it('should be valid if model has been specified', function() {
+ $rootScope.date = new Date();
+ $rootScope.$digest();
+ expect(inputEl.hasClass('ng-valid')).toBeTruthy();
+ });
+ });
+
+ });
+
});
describe('datepicker directive with empty initial state', function () {
@@ -736,7 +1104,7 @@ describe('datepicker directive with empty initial state', function () {
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.date = null;
- element = $compile('
')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
}));
diff --git a/template/datepicker/datepicker.html b/template/datepicker/datepicker.html
index 708f4f7577..bc54142737 100644
--- a/template/datepicker/datepicker.html
+++ b/template/datepicker/datepicker.html
@@ -1,4 +1,4 @@
-
+
|
@@ -14,7 +14,7 @@
{{ getWeekNumber(row) }} |
-
+
|
diff --git a/template/datepicker/popup.html b/template/datepicker/popup.html
new file mode 100644
index 0000000000..77b04a263e
--- /dev/null
+++ b/template/datepicker/popup.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file