Skip to content

Commit 0ed5a2d

Browse files
committed
fix(date-picker): fix validation of date picker, #6471
1 parent 4027a4a commit 0ed5a2d

File tree

4 files changed

+126
-56
lines changed

4 files changed

+126
-56
lines changed

projects/igniteui-angular/src/lib/calendar/calendar-base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
620620
* Deselects date(s) (based on the selection type).
621621
*/
622622
public deselectDate(value?: Date | Date[]) {
623-
if (this.selectedDates === null || this.selectedDates.length === 0) {
623+
if (this.selectedDates || this.selectedDates.length === 0) {
624624
return;
625625
}
626626

projects/igniteui-angular/src/lib/date-picker/date-picker.component.html

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,42 @@
11
<ng-template #readOnlyDatePickerTemplate>
2-
<igx-input-group (click)="openDialog()">
2+
<igx-input-group (click)="openDialog()" (mousedown)="mouseDown($event)">
33
<igx-prefix>
44
<igx-icon>today</igx-icon>
55
</igx-prefix>
66
<label *ngIf="labelVisibility" igxLabel>{{label}}</label>
7-
<input #readonlyInput class="igx-date-picker__input-date" igxInput [value]="displayData || ''"
8-
[disabled]="disabled" readonly />
7+
<input #readonlyInput
8+
class="igx-date-picker__input-date"
9+
igxInput
10+
[value]="displayData || ''"
11+
[disabled]="disabled"
12+
(blur)="onBlur($event)"
13+
readonly
14+
/>
915
</igx-input-group>
1016
</ng-template>
1117

1218
<ng-template #editableDatePickerTemplate>
13-
<igx-input-group #editableInputGroup [supressInputAutofocus]="true">
19+
<igx-input-group #editableInputGroup [supressInputAutofocus]="true" (mousedown)="mouseDown($event)">
1420
<igx-prefix (click)="openDialog(editableInputGroup.element.nativeElement)">
1521
<igx-icon>today</igx-icon>
1622
</igx-prefix>
1723
<label *ngIf="labelVisibility" igxLabel>{{label}}</label>
18-
<input #editableInput class="igx-date-picker__input-date" igxInput [igxTextSelection]="true"
19-
type="text" [value]="transformedDate"
20-
[igxMask]="inputMask" [placeholder]="mask" [disabled]="disabled" [displayValuePipe]="displayValuePipe"
21-
[focusedValuePipe]="inputValuePipe" (blur)="onBlur($event)" (wheel)="onWheel($event)"
22-
(input)="onInput($event)" (focus)="onFocus()" />
24+
<input #editableInput
25+
class="igx-date-picker__input-date"
26+
igxInput
27+
[igxTextSelection]="true"
28+
type="text"
29+
[value]="transformedDate"
30+
[igxMask]="inputMask"
31+
[placeholder]="mask"
32+
[disabled]="disabled"
33+
[displayValuePipe]="displayValuePipe"
34+
[focusedValuePipe]="inputValuePipe"
35+
(blur)="onBlur($event)"
36+
(wheel)="onWheel($event)"
37+
(input)="onInput($event)"
38+
(focus)="onFocus()"
39+
/>
2340
<igx-suffix *ngIf="!isEmpty" (click)="clear()">
2441
<igx-icon>clear</igx-icon>
2542
</igx-suffix>

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ describe('IgxDatePicker', () => {
12601260
}));
12611261
});
12621262

1263-
fdescribe('Reactive form', () => {
1263+
describe('Reactive form', () => {
12641264
let fixture: ComponentFixture<IgxDatePickerReactiveFormComponent>;
12651265
let datePicker: IgxDatePickerComponent;
12661266

projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts

+98-45
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import {
1616
HostListener,
1717
NgModuleRef,
1818
OnInit,
19-
AfterViewInit
19+
AfterViewInit,
20+
Injector
2021
} from '@angular/core';
21-
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
22+
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms';
2223
import {
2324
IgxCalendarComponent,
2425
IgxCalendarHeaderTemplateDirective,
@@ -28,8 +29,8 @@ import {
2829
isDateInRanges
2930
} from '../calendar/index';
3031
import { IgxIconModule } from '../icon/index';
31-
import { IgxInputGroupModule, IgxInputDirective, IgxInputGroupComponent } from '../input-group/index';
32-
import { Subject, fromEvent, animationFrameScheduler, interval } from 'rxjs';
32+
import { IgxInputGroupModule, IgxInputDirective, IgxInputGroupComponent, IgxInputState } from '../input-group/index';
33+
import { Subject, fromEvent, animationFrameScheduler, interval, Subscription } from 'rxjs';
3334
import { filter, takeUntil, throttle } from 'rxjs/operators';
3435
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
3536
import { IgxTextSelectionModule} from '../directives/text-selection/text-selection.directive';
@@ -113,6 +114,8 @@ export enum PredefinedFormatOptions {
113114
FullDate = 'fullDate'
114115
}
115116

117+
const noop = () => { };
118+
116119
/**
117120
* **Ignite UI for Angular Date Picker** -
118121
* [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date_picker.html)
@@ -419,8 +422,12 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
419422
this._transformedDate = value;
420423
}
421424

422-
constructor(@Inject(IgxOverlayService) private _overlayService: IgxOverlayService, public element: ElementRef,
423-
private _cdr: ChangeDetectorRef, private _moduleRef: NgModuleRef<any>) { }
425+
constructor(@Inject(
426+
IgxOverlayService) private _overlayService: IgxOverlayService,
427+
public element: ElementRef,
428+
private _cdr: ChangeDetectorRef,
429+
private _moduleRef: NgModuleRef<any>,
430+
private _injector: Injector) { }
424431

425432
/**
426433
* Gets the input group template.
@@ -722,9 +729,15 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
722729
@ViewChild('readonlyInput', { read: ElementRef })
723730
protected readonlyInput: ElementRef;
724731

725-
/*
726-
* @hidden
727-
*/
732+
/* @hidden */
733+
@ViewChild('editableInput', { read: IgxInputDirective })
734+
protected editableInputDirective: IgxInputDirective;
735+
736+
/* @hidden */
737+
@ViewChild('readonlyInput', { read: IgxInputDirective })
738+
protected readonlyInputDirective: IgxInputDirective;
739+
740+
/** @hidden @internal */
728741
@ContentChild(IgxInputDirective)
729742
protected input: IgxInputDirective;
730743

@@ -778,6 +791,7 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
778791
year: false
779792
};
780793
private _destroy$ = new Subject<boolean>();
794+
private _statusChanges$: Subscription;
781795
private _componentID: string;
782796
private _format: string;
783797
private _value: Date;
@@ -792,50 +806,43 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
792806
private _transformedDate;
793807
private _onOpen = new EventEmitter<IgxDatePickerComponent>();
794808
private _onClose = new EventEmitter<IgxDatePickerComponent>();
809+
private _ngControl: NgControl = null;
795810

796-
/**
797-
* @hidden
798-
*/
799-
@HostListener('keydown.spacebar', ['$event'])
800-
@HostListener('keydown.space', ['$event'])
801-
public onSpaceClick(event: KeyboardEvent) {
802-
this.openDialog(this.getInputGroupElement());
803-
event.preventDefault();
804-
}
811+
//#region ControlValueAccessor
805812

806-
/**
807-
*Method that sets the selected date.
808-
*```typescript
809-
*public date = new Date();
810-
*@ViewChild("MyDatePicker")
811-
*public datePicker: IgxDatePickerComponent;
812-
*ngAfterViewInit(){
813-
* this.datePicker.writeValue(this.date);
814-
*}
815-
*```
816-
*@param value The date you want to select.
817-
*@memberOf {@link IgxDatePickerComponent}
818-
*/
813+
private _onChangeCallback: (_: Date) => void = noop;
814+
815+
private _onTouchedCallback: () => void = noop;
816+
817+
/** @hidden @internal */
819818
public writeValue(value: Date) {
820-
this.value = value;
819+
this._value = value;
820+
// TODO: do we need next call
821821
this._cdr.markForCheck();
822822
}
823823

824-
/**
825-
*@hidden
826-
*/
824+
/** @hidden @internal */
827825
public registerOnChange(fn: (_: Date) => void) { this._onChangeCallback = fn; }
828826

829-
/**
830-
*@hidden
831-
*/
827+
/** @hidden @internal */
832828
public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; }
833829

834-
/**
835-
*@hidden
836-
*/
830+
/** @hidden @internal */
837831
public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; }
838832

833+
//#endregion
834+
835+
/**
836+
* @hidden
837+
*/
838+
@HostListener('keydown.spacebar', ['$event'])
839+
@HostListener('keydown.space', ['$event'])
840+
public onSpaceClick(event: KeyboardEvent) {
841+
this.openDialog(this.getInputGroupElement());
842+
event.preventDefault();
843+
}
844+
845+
839846
/** @hidden */
840847
public getEditElement() {
841848
const inputElement = this.editableInput || this.readonlyInput || this.input;
@@ -910,6 +917,8 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
910917
}
911918
this.inputMask = DatePickerUtil.getInputMask(this.dateFormatParts);
912919
}
920+
921+
this._ngControl = this._injector.get<NgControl>(NgControl, null);
913922
}
914923

915924
ngAfterViewInit() {
@@ -919,6 +928,32 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
919928
takeUntil(this._destroy$)
920929
).subscribe((res) => this.onKeyDown(res));
921930
}
931+
932+
if (this._ngControl) {
933+
this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
934+
}
935+
}
936+
937+
protected onStatusChanged() {
938+
if ((this._ngControl.control.touched || this._ngControl.control.dirty) &&
939+
(this._ngControl.control.validator || this._ngControl.control.asyncValidator)) {
940+
const input = this.readonlyInputDirective || this.editableInputDirective;
941+
if (this.inputGroup.isFocused) {
942+
input.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
943+
} else {
944+
input.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
945+
}
946+
}
947+
this.manageRequiredAsterisk();
948+
}
949+
950+
protected manageRequiredAsterisk(): void {
951+
if (this._ngControl && this._ngControl.control.validator) {
952+
// Run the validation with empty object to check if required is enabled.
953+
const error = this._ngControl.control.validator({} as AbstractControl);
954+
this.inputGroup.isRequired = error && error.required;
955+
this._cdr.markForCheck();
956+
}
922957
}
923958

924959
/**
@@ -1031,6 +1066,14 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
10311066
}
10321067
}
10331068

1069+
public mouseDown(e) {
1070+
// if the click is not on the input but in input group
1071+
// e.g. on prefix or sufix, prevent default and this way prevent blur
1072+
if (e.target !== this.getEditElement()) {
1073+
e.preventDefault();
1074+
}
1075+
}
1076+
10341077
/**
10351078
* Close the calendar.
10361079
*
@@ -1086,7 +1129,20 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
10861129
*/
10871130
public onBlur(event): void {
10881131
this._isInEditMode = false;
1089-
this.calculateDate(event.target.value, event.type);
1132+
if (this.mode === InteractionMode.DropDown) {
1133+
this.calculateDate(event.target.value, event.type);
1134+
}
1135+
1136+
this._onTouchedCallback();
1137+
if (this.collapsed) {
1138+
const input = this.readonlyInputDirective || this.editableInputDirective || this.input;
1139+
if (this._ngControl && !this._ngControl.valid) {
1140+
console.log('onBlur + collapsed + invalid');
1141+
input.valid = IgxInputState.INVALID;
1142+
} else {
1143+
input.valid = IgxInputState.INITIAL;
1144+
}
1145+
}
10901146
}
10911147

10921148
/**
@@ -1373,9 +1429,6 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
13731429
return DatePickerUtil.addPromptCharsEditMode(this.dateFormatParts, this.value, changedValue);
13741430
}
13751431

1376-
private _onTouchedCallback: () => void = () => { };
1377-
1378-
private _onChangeCallback: (_: Date) => void = () => { };
13791432
}
13801433

13811434
/**

0 commit comments

Comments
 (0)