diff --git a/src/components/datepicker/datePicker.js b/src/components/datepicker/datePicker.js index cb13b55c342..45ffdc474fe 100644 --- a/src/components/datepicker/datePicker.js +++ b/src/components/datepicker/datePicker.js @@ -265,7 +265,7 @@ self.date = value; self.inputElement.value = self.dateLocale.formatDate(value); self.resizeInputElement(); - self.setErrorFlags(); + self.updateErrorState(); }; }; @@ -283,7 +283,7 @@ self.inputElement.value = self.dateLocale.formatDate(date); self.closeCalendarPane(); self.resizeInputElement(); - self.inputContainer.classList.remove(INVALID_CLASS); + self.updateErrorState(); }); self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement)); @@ -350,12 +350,19 @@ * Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are: * - mindate: whether the selected date is before the minimum date. * - maxdate: whether the selected flag is after the maximum date. + * - filtered: whether the selected date is allowed by the custom filtering function. + * - valid: whether the entered text input is a valid date + * + * The 'required' flag is handled automatically by ngModel. * * @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value. */ - DatePickerCtrl.prototype.setErrorFlags = function(opt_date) { + DatePickerCtrl.prototype.updateErrorState = function(opt_date) { var date = opt_date || this.date; + // Clear any existing errors to get rid of anything that's no longer relevant. + this.clearErrorState(); + if (this.dateUtil.isValidDate(date)) { if (this.dateUtil.isValidDate(this.minDate)) { this.ngModelCtrl.$setValidity('mindate', date >= this.minDate); @@ -366,11 +373,30 @@ } if (angular.isFunction(this.dateFilter)) { - this.ngModelCtrl.$setValidity('filtered', this.dateFilter(this.date)); + this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date)); } + } else { + // The date is seen as "not a valid date" if there is *something* set + // (i.e.., not null or undefined), but that something isn't a valid date. + this.ngModelCtrl.$setValidity('valid', date == null); + } + + // TODO(jelbourn): Change this to classList.toggle when we stop using PhantomJS in unit tests + // because it doesn't conform to the DOMTokenList spec. + // See https://github.com/ariya/phantomjs/issues/12782. + if (!this.ngModelCtrl.$valid) { + this.inputContainer.classList.add(INVALID_CLASS); } }; + /** Clears any error flags set by `updateErrorState`. */ + DatePickerCtrl.prototype.clearErrorState = function() { + this.inputContainer.classList.remove(INVALID_CLASS); + ['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) { + this.ngModelCtrl.$setValidity(field, true); + }, this); + }; + /** Resizes the input element based on the size of its content. */ DatePickerCtrl.prototype.resizeInputElement = function() { this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE; @@ -382,24 +408,24 @@ */ DatePickerCtrl.prototype.handleInputEvent = function() { var inputString = this.inputElement.value; - var parsedDate = this.dateLocale.parseDate(inputString); + var parsedDate = inputString ? this.dateLocale.parseDate(inputString) : null; this.dateUtil.setDateTimeToMidnight(parsedDate); - if (inputString === '') { - this.ngModelCtrl.$setViewValue(null); - this.date = null; - this.inputContainer.classList.remove(INVALID_CLASS); - } else if (this.dateUtil.isValidDate(parsedDate) && - this.dateLocale.isDateComplete(inputString) && - this.isDateEnabled(parsedDate)) { + + // An input string is valid if it is either empty (representing no date) + // or if it parses to a valid date that the user is allowed to select. + var isValidInput = inputString == '' || ( + this.dateUtil.isValidDate(parsedDate) && + this.dateLocale.isDateComplete(inputString) && + this.isDateEnabled(parsedDate) + ); + + // The datepicker's model is only updated when there is a valid input. + if (isValidInput) { this.ngModelCtrl.$setViewValue(parsedDate); this.date = parsedDate; - this.setErrorFlags(); - this.inputContainer.classList.remove(INVALID_CLASS); - } else { - // If there's an input string, it's an invalid date. - this.setErrorFlags(parsedDate); - this.inputContainer.classList.toggle(INVALID_CLASS, inputString); } + + this.updateErrorState(parsedDate); }; /** diff --git a/src/components/datepicker/datePicker.spec.js b/src/components/datepicker/datePicker.spec.js index 9c030ce0824..91fc349ca4c 100644 --- a/src/components/datepicker/datePicker.spec.js +++ b/src/components/datepicker/datePicker.spec.js @@ -245,7 +245,7 @@ describe('md-date-picker', function() { populateInputElement('6/1/2015'); expect(controller.inputContainer).not.toHaveClass('md-datepicker-invalid'); - populateInputElement('7'); + populateInputElement('cheese'); expect(controller.inputContainer).toHaveClass('md-datepicker-invalid'); }); @@ -258,6 +258,14 @@ describe('md-date-picker', function() { populateInputElement('5/30/2014'); expect(controller.ngModelCtrl.$modelValue).toEqual(initialDate); }); + + it('should not update the input string is not "complete"', function() { + var date = new Date(2015, DEC, 1); + pageScope.myDate = date; + + populateInputElement('7'); + expect(pageScope.myDate).toEqual(date); + }); }); describe('floating calendar pane', function() { @@ -452,12 +460,13 @@ describe('md-date-picker', function() { }); it('should remove the invalid state if present', function() { - populateInputElement('7'); + populateInputElement('cheese'); expect(controller.inputContainer).toHaveClass('md-datepicker-invalid'); controller.openCalendarPane({ target: controller.inputElement }); + scope.$emit('md-calendar-change', new Date()); expect(controller.inputContainer).not.toHaveClass('md-datepicker-invalid'); }); diff --git a/src/components/datepicker/demoBasicUsage/index.html b/src/components/datepicker/demoBasicUsage/index.html index 21d5115c72d..58e77b6f01c 100644 --- a/src/components/datepicker/demoBasicUsage/index.html +++ b/src/components/datepicker/demoBasicUsage/index.html @@ -13,7 +13,7 @@

Date-picker with min date and max date

Only weekends are selectable

- +

Only weekends within given range are selectable

With ngMessages md-date-filter="onlyWeekendsPredicate">
+
The entered value is not a date!
This date is required!
Date is too early!
Date is too late!