diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts index 399cede3c4a..3683d183383 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts @@ -33,7 +33,8 @@ describe('IgxInput', () => { RequiredTwoWayDataBoundInputComponent, DataBoundDisabledInputComponent, ReactiveFormComponent, - InputsWithSameNameAttributesComponent + InputsWithSameNameAttributesComponent, + ToggleRequiredWithNgModelInputComponent ], imports: [ IgxInputGroupModule, @@ -247,6 +248,7 @@ describe('IgxInput', () => { }); it('When updating two inputs with same attribute names through ngModel, label should responds', fakeAsync(() => { + const fix = TestBed.createComponent(InputsWithSameNameAttributesComponent); fix.detectChanges(); @@ -319,6 +321,116 @@ describe('IgxInput', () => { fix.detectChanges(); expect(firstInputGroup.nativeElement.classList.contains('igx-input-group--invalid')).toBe(true); })); + + it('Should style input when required is toggled dynamically.', () => { + const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent); + fixture.detectChanges(); + + const instance = fixture.componentInstance; + const input = instance.igxInputs.toArray()[1]; + const inputGroup = instance.igxInputGroups.toArray()[1]; + + expect(input.required).toBe(false); + expect(inputGroup.isRequired).toBeFalsy(); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false); + + dispatchInputEvent('focus', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + + input.value = '123'; + dispatchInputEvent('input', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false); + + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false); + + instance.isRequired = true; + fixture.detectChanges(); + + expect(input.required).toBe(true); + + expect(inputGroup.isRequired).toBeTruthy(); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true); + + dispatchInputEvent('focus', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + + input.value = ''; + dispatchInputEvent('input', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + + input.value = '123'; + dispatchInputEvent('input', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true); + + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false); + }); + + it('Should style input with ngModel when required is toggled dynamically.', () => { + const fixture = TestBed.createComponent(ToggleRequiredWithNgModelInputComponent); + fixture.detectChanges(); + + const instance = fixture.componentInstance; + const input = instance.igxInputs.toArray()[0]; + const inputGroup = instance.igxInputGroups.toArray()[0]; + + expect(input.required).toBe(false); + expect(inputGroup.isRequired).toBeFalsy(); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(false); + + dispatchInputEvent('focus', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + + input.value = '123'; + dispatchInputEvent('input', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true); + + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false); + + instance.isRequired = true; + fixture.detectChanges(); + + expect(input.required).toBe(true); + + expect(inputGroup.isRequired).toBeTruthy(); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true); + + dispatchInputEvent('focus', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + + input.value = ''; + dispatchInputEvent('input', input.nativeElement, fixture); + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + + dispatchInputEvent('focus', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + + input.value = '123'; + dispatchInputEvent('input', input.nativeElement, fixture); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(true); + + dispatchInputEvent('blur', input.nativeElement, fixture); + expect(input.valid).toBe(IgxInputState.INITIAL); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); + expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false); + }); }); @Component({ template: ` @@ -529,6 +641,26 @@ class ReactiveFormComponent { } } +@Component({ template: ` + + + + + + + ` }) +class ToggleRequiredWithNgModelInputComponent { + @ViewChildren(IgxInputDirective) + public igxInputs: QueryList; + + @ViewChildren(IgxInputGroupComponent) + public igxInputGroups: QueryList; + + public data = ''; + public data1 = ''; + public isRequired = false; +} + function testRequiredValidation(inputElement, fixture) { dispatchInputEvent('focus', inputElement, fixture); inputElement.value = 'test'; diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts index 959426820fd..e7e279ff909 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts @@ -24,7 +24,8 @@ export enum IgxInputState { } @Directive({ - selector: '[igxInput]' + selector: '[igxInput]', + exportAs: 'igxInput' }) export class IgxInputDirective implements AfterViewInit, OnDestroy { private _valid = IgxInputState.INITIAL; @@ -92,6 +93,40 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { public get disabled() { return this.nativeElement.hasAttribute('disabled'); } + + /** + * Sets the `required` property. + * ```html + * + * + * + * ``` + * @memberof IgxInputDirective + */ + @Input() + public set required(value: boolean) { + if (typeof value === 'boolean') { + this.nativeElement.required = this.inputGroup.isRequired = value; + + if (value && !this.nativeElement.checkValidity()) { + this._valid = IgxInputState.INVALID; + } else { + this._valid = IgxInputState.INITIAL; + } + } + } + + /** + * Gets whether the igxInput is required. + * ```typescript + * let isRequired = this.igxInput.required; + * ``` + * @memberof IgxInputDirective + */ + public get required() { + return this.nativeElement.hasAttribute('required'); + } + /** * Sets/gets whether the `"igx-input-group__input"` class is added to the host element. * Default value is `false`. @@ -227,16 +262,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { } } } - /** - * Gets whether the igxInput is required. - * ```typescript - * let isRequired = this.igxInput.required; - * ``` - * @memberof IgxInputDirective - */ - public get required() { - return this.nativeElement.hasAttribute('required'); - } /** * Gets whether the igxInput has a placeholder. * ```typescript @@ -287,6 +312,18 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { public get valid(): IgxInputState { return this._valid; } + + /** + * Gets whether the igxInput is valid. + * ```typescript + * let valid = this.igxInput.isValid; + * ``` + * @memberof IgxInputDirective + */ + public get isValid(): boolean { + return this.valid !== IgxInputState.INVALID; + } + /** * Sets the state of the igxInput. * ```typescript diff --git a/src/app/input-group/input-group.sample.html b/src/app/input-group/input-group.sample.html index c22bfdc6149..b03e0f4793c 100644 --- a/src/app/input-group/input-group.sample.html +++ b/src/app/input-group/input-group.sample.html @@ -2,6 +2,16 @@
+

Line Style Input with required @Input

+
+ + + + This is required + + +
+

Line Style Input

diff --git a/src/app/input-group/input-group.sample.ts b/src/app/input-group/input-group.sample.ts index 50d44de4fff..67e8f618620 100644 --- a/src/app/input-group/input-group.sample.ts +++ b/src/app/input-group/input-group.sample.ts @@ -11,4 +11,11 @@ export class InputGroupSampleComponent { firstName: 'Oke', lastName: 'Nduka' }; + + public isRequired = true; + public value = ''; + + public toggleRequired() { + this.isRequired = !this.isRequired; + } } diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index a8d64a01316..72adefb9686 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -5,7 +5,7 @@

Time Picker with Dropdown

-
{{showDate()}}
+
{{showDate(date)}}
Time Picker with Dropdown

Horizontal Time Picker

AM/PM Time format

-

@@ -51,5 +50,27 @@

Templated Time Picker

+
+

Templated Time Picker

+

Time picker with required input group

+
+ + + + + + access_time + + + + star + + + + +
+
\ No newline at end of file diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 74ad38fbf74..d76b712c778 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -7,19 +7,29 @@ import { IgxTimePickerComponent, InteractionMode } from 'igniteui-angular'; templateUrl: 'time-picker.sample.html' }) export class TimePickerSampleComponent { - max = "19:00"; - min = "09:00"; + max = '19:00'; + min = '09:00'; itemsDelta = { hours: 1, minutes: 5 }; - format = "hh:mm tt"; + format = 'hh:mm tt'; isSpinLoop = true; isVertical = true; - public mode = InteractionMode.DropDown; + mode = InteractionMode.DropDown; date = new Date(2018, 10, 27, 17, 45, 0, 0); + today = new Date(Date.now()); - showDate() { - return this.date ? this.date.toLocaleString() : 'Value is null.'; + isRequired = true; + + @ViewChild('tp', { read: IgxTimePickerComponent }) + public tp: IgxTimePickerComponent; + + showDate(date) { + return date ? date.toLocaleString() : 'Value is null.'; + } + + change() { + this.isRequired = !this.isRequired; } valueChanged(event) { @@ -29,7 +39,4 @@ export class TimePickerSampleComponent { validationFailed(event) { console.log(event); } - - @ViewChild('tp', { read: IgxTimePickerComponent }) - public tp: IgxTimePickerComponent; }