Skip to content

Commit

Permalink
fix(datepicker): improve error state updating. Fixes angular#5315
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn committed Nov 30, 2015
1 parent 34161fc commit d5b72df
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 21 deletions.
62 changes: 44 additions & 18 deletions src/components/datepicker/datePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@
self.date = value;
self.inputElement.value = self.dateLocale.formatDate(value);
self.resizeInputElement();
self.setErrorFlags();
self.updateErrorState();
};
};

Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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);
};

/**
Expand Down
13 changes: 11 additions & 2 deletions src/components/datepicker/datePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand All @@ -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() {
Expand Down Expand Up @@ -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');
});
Expand Down
3 changes: 2 additions & 1 deletion src/components/datepicker/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h4>Date-picker with min date and max date</h4>
<h4>Only weekends are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<h4>Only weekends within given range are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"
Expand All @@ -26,6 +26,7 @@ <h4>With ngMessages</h4>
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<div class="validation-messages" ng-messages="myForm.dateField.$error">
<div ng-message="valid">The entered value is not a date!</div>
<div ng-message="required">This date is required!</div>
<div ng-message="mindate">Date is too early!</div>
<div ng-message="maxdate">Date is too late!</div>
Expand Down

0 comments on commit d5b72df

Please sign in to comment.