Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit d5b72df

Browse files
committed
fix(datepicker): improve error state updating. Fixes #5315
1 parent 34161fc commit d5b72df

File tree

3 files changed

+57
-21
lines changed

3 files changed

+57
-21
lines changed

src/components/datepicker/datePicker.js

+44-18
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@
265265
self.date = value;
266266
self.inputElement.value = self.dateLocale.formatDate(value);
267267
self.resizeInputElement();
268-
self.setErrorFlags();
268+
self.updateErrorState();
269269
};
270270
};
271271

@@ -283,7 +283,7 @@
283283
self.inputElement.value = self.dateLocale.formatDate(date);
284284
self.closeCalendarPane();
285285
self.resizeInputElement();
286-
self.inputContainer.classList.remove(INVALID_CLASS);
286+
self.updateErrorState();
287287
});
288288

289289
self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
@@ -350,12 +350,19 @@
350350
* Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
351351
* - mindate: whether the selected date is before the minimum date.
352352
* - maxdate: whether the selected flag is after the maximum date.
353+
* - filtered: whether the selected date is allowed by the custom filtering function.
354+
* - valid: whether the entered text input is a valid date
355+
*
356+
* The 'required' flag is handled automatically by ngModel.
353357
*
354358
* @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
355359
*/
356-
DatePickerCtrl.prototype.setErrorFlags = function(opt_date) {
360+
DatePickerCtrl.prototype.updateErrorState = function(opt_date) {
357361
var date = opt_date || this.date;
358362

363+
// Clear any existing errors to get rid of anything that's no longer relevant.
364+
this.clearErrorState();
365+
359366
if (this.dateUtil.isValidDate(date)) {
360367
if (this.dateUtil.isValidDate(this.minDate)) {
361368
this.ngModelCtrl.$setValidity('mindate', date >= this.minDate);
@@ -366,11 +373,30 @@
366373
}
367374

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

392+
/** Clears any error flags set by `updateErrorState`. */
393+
DatePickerCtrl.prototype.clearErrorState = function() {
394+
this.inputContainer.classList.remove(INVALID_CLASS);
395+
['mindate', 'maxdate', 'filtered', 'valid'].forEach(function(field) {
396+
this.ngModelCtrl.$setValidity(field, true);
397+
}, this);
398+
};
399+
374400
/** Resizes the input element based on the size of its content. */
375401
DatePickerCtrl.prototype.resizeInputElement = function() {
376402
this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
@@ -382,24 +408,24 @@
382408
*/
383409
DatePickerCtrl.prototype.handleInputEvent = function() {
384410
var inputString = this.inputElement.value;
385-
var parsedDate = this.dateLocale.parseDate(inputString);
411+
var parsedDate = inputString ? this.dateLocale.parseDate(inputString) : null;
386412
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)) {
413+
414+
// An input string is valid if it is either empty (representing no date)
415+
// or if it parses to a valid date that the user is allowed to select.
416+
var isValidInput = inputString == '' || (
417+
this.dateUtil.isValidDate(parsedDate) &&
418+
this.dateLocale.isDateComplete(inputString) &&
419+
this.isDateEnabled(parsedDate)
420+
);
421+
422+
// The datepicker's model is only updated when there is a valid input.
423+
if (isValidInput) {
394424
this.ngModelCtrl.$setViewValue(parsedDate);
395425
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);
402426
}
427+
428+
this.updateErrorState(parsedDate);
403429
};
404430

405431
/**

src/components/datepicker/datePicker.spec.js

+11-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

@@ -258,6 +258,14 @@ describe('md-date-picker', function() {
258258
populateInputElement('5/30/2014');
259259
expect(controller.ngModelCtrl.$modelValue).toEqual(initialDate);
260260
});
261+
262+
it('should not update the input string is not "complete"', function() {
263+
var date = new Date(2015, DEC, 1);
264+
pageScope.myDate = date;
265+
266+
populateInputElement('7');
267+
expect(pageScope.myDate).toEqual(date);
268+
});
261269
});
262270

263271
describe('floating calendar pane', function() {
@@ -452,12 +460,13 @@ describe('md-date-picker', function() {
452460
});
453461

454462
it('should remove the invalid state if present', function() {
455-
populateInputElement('7');
463+
populateInputElement('cheese');
456464
expect(controller.inputContainer).toHaveClass('md-datepicker-invalid');
457465

458466
controller.openCalendarPane({
459467
target: controller.inputElement
460468
});
469+
461470
scope.$emit('md-calendar-change', new Date());
462471
expect(controller.inputContainer).not.toHaveClass('md-datepicker-invalid');
463472
});

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)