Skip to content

Commit 6d64072

Browse files
committed
fix(datepicker): improve error state updating. Fixes angular#5315
1 parent b8ffdfe commit 6d64072

File tree

3 files changed

+50
-21
lines changed

3 files changed

+50
-21
lines changed

src/components/datepicker/datePicker.js

+45-18
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@
265265
self.date = value;
266266
self.inputElement.value = self.dateLocale.formatDate(value);
267267
self.resizeInputElement();
268-
self.setErrorFlags();
268+
269+
self.updateErrorState();
269270
};
270271
};
271272

@@ -283,7 +284,7 @@
283284
self.inputElement.value = self.dateLocale.formatDate(date);
284285
self.closeCalendarPane();
285286
self.resizeInputElement();
286-
self.inputContainer.classList.remove(INVALID_CLASS);
287+
self.updateErrorState();
287288
});
288289

289290
self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
@@ -350,12 +351,19 @@
350351
* Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
351352
* - mindate: whether the selected date is before the minimum date.
352353
* - maxdate: whether the selected flag is after the maximum date.
354+
* - filtered: whether the selected date is allowed by the custom filtering function.
355+
* - valid: whether the entered text input is a valid date
356+
*
357+
* The 'required' flag is handled automatically by ngModel.
353358
*
354359
* @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
355360
*/
356-
DatePickerCtrl.prototype.setErrorFlags = function(opt_date) {
361+
DatePickerCtrl.prototype.updateErrorState = function(opt_date) {
357362
var date = opt_date || this.date;
358363

364+
// Clear any existing errors to get rid of anything that's no longer relevant.
365+
this.clearErrorState();
366+
359367
if (this.dateUtil.isValidDate(date)) {
360368
if (this.dateUtil.isValidDate(this.minDate)) {
361369
this.ngModelCtrl.$setValidity('mindate', date >= this.minDate);
@@ -366,11 +374,30 @@
366374
}
367375

368376
if (angular.isFunction(this.dateFilter)) {
369-
this.ngModelCtrl.$setValidity('filtered', this.dateFilter(this.date));
377+
this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date));
370378
}
379+
} else {
380+
// The date is counted as "not a valid date" if there is *something* set
381+
// (i.e.., not null or undefined), but that something isn't a valid date.
382+
this.ngModelCtrl.$setValidity('valid', date == null);
383+
}
384+
385+
// TODO(jelbourn): Change this to clasList.toggle when we stop using PhantomJS in unit tests
386+
// because it doesn't confirm to the DOMTokenList spec.
387+
// See https://github.com/ariya/phantomjs/issues/12782.
388+
if (!this.ngModelCtrl.$valid) {
389+
this.inputContainer.classList.add(INVALID_CLASS);
371390
}
372391
};
373392

393+
/** Clears any error flags set by `updateErrorState`. */
394+
DatePickerCtrl.prototype.clearErrorState = function() {
395+
this.inputContainer.classList.remove(INVALID_CLASS);
396+
['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) {
397+
this.ngModelCtrl.$setValidity(field, true);
398+
}, this);
399+
};
400+
374401
/** Resizes the input element based on the size of its content. */
375402
DatePickerCtrl.prototype.resizeInputElement = function() {
376403
this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
@@ -382,24 +409,24 @@
382409
*/
383410
DatePickerCtrl.prototype.handleInputEvent = function() {
384411
var inputString = this.inputElement.value;
385-
var parsedDate = this.dateLocale.parseDate(inputString);
412+
var parsedDate = inputString ? this.dateLocale.parseDate(inputString) : null;
386413
this.dateUtil.setDateTimeToMidnight(parsedDate);
387-
if (inputString === '') {
388-
this.ngModelCtrl.$setViewValue(null);
389-
this.date = null;
390-
this.inputContainer.classList.remove(INVALID_CLASS);
391-
} else if (this.dateUtil.isValidDate(parsedDate) &&
392-
this.dateLocale.isDateComplete(inputString) &&
393-
this.isDateEnabled(parsedDate)) {
414+
415+
// An input string is valid if it is either empty (representing no date)
416+
// or if it parses to a valid date that the user is allowed to select.
417+
var isValidInput = inputString == '' || (
418+
this.dateUtil.isValidDate(parsedDate) &&
419+
this.dateLocale.isDateComplete(inputString) &&
420+
this.isDateEnabled(parsedDate)
421+
);
422+
423+
// The datepicker's model is only updated when there is a valid input.
424+
if (isValidInput) {
394425
this.ngModelCtrl.$setViewValue(parsedDate);
395426
this.date = parsedDate;
396-
this.setErrorFlags();
397-
this.inputContainer.classList.remove(INVALID_CLASS);
398-
} else {
399-
// If there's an input string, it's an invalid date.
400-
this.setErrorFlags(parsedDate);
401-
this.inputContainer.classList.toggle(INVALID_CLASS, inputString);
402427
}
428+
429+
this.updateErrorState(parsedDate);
403430
};
404431

405432
/**

src/components/datepicker/datePicker.spec.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ describe('md-date-picker', function() {
245245
populateInputElement('6/1/2015');
246246
expect(controller.inputContainer).not.toHaveClass('md-datepicker-invalid');
247247

248-
populateInputElement('7');
248+
populateInputElement('cheese');
249249
expect(controller.inputContainer).toHaveClass('md-datepicker-invalid');
250250
});
251251

@@ -422,12 +422,13 @@ describe('md-date-picker', function() {
422422
});
423423

424424
it('should remove the invalid state if present', function() {
425-
populateInputElement('7');
425+
populateInputElement('cheese');
426426
expect(controller.inputContainer).toHaveClass('md-datepicker-invalid');
427427

428428
controller.openCalendarPane({
429429
target: controller.inputElement
430430
});
431+
431432
scope.$emit('md-calendar-change', new Date());
432433
expect(controller.inputContainer).not.toHaveClass('md-datepicker-invalid');
433434
});

src/components/datepicker/demoBasicUsage/index.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ <h4>Date-picker with min date and max date</h4>
1313
<h4>Only weekends are selectable</h4>
1414
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
1515
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
16-
16+
1717
<h4>Only weekends within given range are selectable</h4>
1818
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
1919
md-min-date="minDate" md-max-date="maxDate"
@@ -26,6 +26,7 @@ <h4>With ngMessages</h4>
2626
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
2727

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

0 commit comments

Comments
 (0)