|
265 | 265 | self.date = value;
|
266 | 266 | self.inputElement.value = self.dateLocale.formatDate(value);
|
267 | 267 | self.resizeInputElement();
|
268 |
| - self.setErrorFlags(); |
| 268 | + |
| 269 | + self.updateErrorState(); |
269 | 270 | };
|
270 | 271 | };
|
271 | 272 |
|
|
283 | 284 | self.inputElement.value = self.dateLocale.formatDate(date);
|
284 | 285 | self.closeCalendarPane();
|
285 | 286 | self.resizeInputElement();
|
286 |
| - self.inputContainer.classList.remove(INVALID_CLASS); |
| 287 | + self.updateErrorState(); |
287 | 288 | });
|
288 | 289 |
|
289 | 290 | self.ngInputElement.on('input', angular.bind(self, self.resizeInputElement));
|
|
350 | 351 | * Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
|
351 | 352 | * - mindate: whether the selected date is before the minimum date.
|
352 | 353 | * - 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. |
353 | 358 | *
|
354 | 359 | * @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
|
355 | 360 | */
|
356 |
| - DatePickerCtrl.prototype.setErrorFlags = function(opt_date) { |
| 361 | + DatePickerCtrl.prototype.updateErrorState = function(opt_date) { |
357 | 362 | var date = opt_date || this.date;
|
358 | 363 |
|
| 364 | + // Clear any existing errors to get rid of anything that's no longer relevant. |
| 365 | + this.clearErrorState(); |
| 366 | + |
359 | 367 | if (this.dateUtil.isValidDate(date)) {
|
360 | 368 | if (this.dateUtil.isValidDate(this.minDate)) {
|
361 | 369 | this.ngModelCtrl.$setValidity('mindate', date >= this.minDate);
|
|
366 | 374 | }
|
367 | 375 |
|
368 | 376 | if (angular.isFunction(this.dateFilter)) {
|
369 |
| - this.ngModelCtrl.$setValidity('filtered', this.dateFilter(this.date)); |
| 377 | + this.ngModelCtrl.$setValidity('filtered', this.dateFilter(date)); |
370 | 378 | }
|
| 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); |
371 | 390 | }
|
372 | 391 | };
|
373 | 392 |
|
| 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 | + |
374 | 401 | /** Resizes the input element based on the size of its content. */
|
375 | 402 | DatePickerCtrl.prototype.resizeInputElement = function() {
|
376 | 403 | this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
|
|
382 | 409 | */
|
383 | 410 | DatePickerCtrl.prototype.handleInputEvent = function() {
|
384 | 411 | var inputString = this.inputElement.value;
|
385 |
| - var parsedDate = this.dateLocale.parseDate(inputString); |
| 412 | + var parsedDate = inputString ? this.dateLocale.parseDate(inputString) : null; |
386 | 413 | 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) { |
394 | 425 | this.ngModelCtrl.$setViewValue(parsedDate);
|
395 | 426 | 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); |
402 | 427 | }
|
| 428 | + |
| 429 | + this.updateErrorState(parsedDate); |
403 | 430 | };
|
404 | 431 |
|
405 | 432 | /**
|
|
0 commit comments