diff --git a/src/app/public/modules/file-attachment/file-attachment.component.html b/src/app/public/modules/file-attachment/file-attachment.component.html index f49cabf3..926df437 100644 --- a/src/app/public/modules/file-attachment/file-attachment.component.html +++ b/src/app/public/modules/file-attachment/file-attachment.component.html @@ -23,6 +23,7 @@ tabindex="-1" type="file" [attr.accept]="acceptedTypes || null" + [disabled]="disabled" [required]="required" (change)="fileChangeEvent($event)" #fileInput @@ -32,6 +33,7 @@ type="button" [attr.aria-label]="'skyux_file_attachment_file_upload_drag_or_click' | skyLibResources" [attr.aria-labelledby]="(hasLabelComponent) ? labelElementId : null" + [disabled]="disabled" (click)="onDropClicked()" > ): DebugElement { + return fixture.debugElement.query(By.css('input')); +} + +function getButtonEl(el: HTMLElement): HTMLElement { + return el.querySelector('.sky-file-attachment-btn'); +} + describe('File attachment', () => { let fixture: ComponentFixture; @@ -60,14 +72,6 @@ describe('File attachment', () => { }); //#region helpers - function getInputDebugEl(): DebugElement { - return fixture.debugElement.query(By.css('input')); - } - - function getButtonEl(): HTMLElement { - return el.querySelector('.sky-file-attachment-btn'); - } - function getDropEl(): HTMLElement { return el.querySelector('.sky-file-attachment'); } @@ -145,7 +149,7 @@ describe('File attachment', () => { } function triggerChangeEvent(expectedChangeFiles: any[]): void { - const inputEl = getInputDebugEl(); + const inputEl = getInputDebugEl(fixture); const fileChangeEvent = { target: { @@ -310,7 +314,7 @@ describe('File attachment', () => { tick(); fixture.detectChanges(); const labelWrapper = getLabelWrapper(); - const input = getInputDebugEl(); + const input = getInputDebugEl(fixture); expect(input.nativeElement.getAttribute('required')).toBeNull(); expect(labelWrapper.classList.contains('sky-control-label-required')).toBe(false); @@ -323,7 +327,7 @@ describe('File attachment', () => { tick(); fixture.detectChanges(); const labelWrapper = getLabelWrapper(); - const input = getInputDebugEl(); + const input = getInputDebugEl(fixture); expect(input.nativeElement.getAttribute('required')).not.toBeNull(); expect(labelWrapper.classList.contains('sky-control-label-required')).toBe(true); @@ -345,13 +349,43 @@ describe('File attachment', () => { tick(); fixture.detectChanges(); const labelWrapper = getLabelWrapper(); - const input = getInputDebugEl(); + const input = getInputDebugEl(fixture); expect(input.nativeElement.getAttribute('required')).not.toBeNull(); expect(labelWrapper.classList.contains('sky-control-label-required')).toBe(true); expect(labelWrapper.getAttribute('aria-required')).toBe('true'); })); + it('should not have disabled attribute when not disabled', fakeAsync(() => { + fileAttachmentInstance.ngAfterViewInit(); + tick(); + fixture.detectChanges(); + const input = getInputDebugEl(fixture); + const button = getButtonEl(el); + + expect(input.nativeElement.getAttribute('disabled')).toBeNull(); + expect(button.getAttribute('disabled')).toBeNull(); + })); + + it(`should have disabled attribute when form control's disabled method is called`, fakeAsync(() => { + fixture.componentInstance.attachment.disable(); + fileAttachmentInstance.ngAfterViewInit(); + tick(); + fixture.detectChanges(); + const input = getInputDebugEl(fixture); + const button = getButtonEl(el); + + expect(input.nativeElement.getAttribute('disabled')).not.toBeNull(); + expect(button.getAttribute('disabled')).not.toBeNull(); + + fixture.componentInstance.attachment.enable(); + tick(); + fixture.detectChanges(); + + expect(input.nativeElement.getAttribute('disabled')).toBeNull(); + expect(button.getAttribute('disabled')).toBeNull(); + })); + it('should handle removing the label', fakeAsync(() => { fixture.componentInstance.required = true; fileAttachmentInstance.ngAfterViewInit(); @@ -372,11 +406,11 @@ describe('File attachment', () => { it('should click the file input on choose file button click', () => { fixture.detectChanges(); - const inputEl = getInputDebugEl(); + const inputEl = getInputDebugEl(fixture); spyOn(inputEl.references.fileInput, 'click'); - const dropEl = getButtonEl(); + const dropEl = getButtonEl(el); expect(inputEl.references.fileInput.click).not.toHaveBeenCalled(); @@ -390,7 +424,7 @@ describe('File attachment', () => { it('should not click the file input on remove button click', () => { fixture.detectChanges(); - const inputEl = getInputDebugEl(); + const inputEl = getInputDebugEl(fixture); spyOn(inputEl.references.fileInput, 'click'); @@ -1060,3 +1094,52 @@ describe('File attachment', () => { }); })); }); + +describe('File attachment (template-driven)', () => { + + let fixture: ComponentFixture; + let fileAttachmentInstance: SkyFileAttachmentComponent; + let el: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + FileAttachmentTestModule + ] + }); + fixture = TestBed.createComponent(TemplateDrivenFileAttachmentTestComponent); + fixture.detectChanges(); + el = fixture.nativeElement; + fileAttachmentInstance = fixture.componentInstance.fileAttachmentComponent; + }); + + it('should not have disabled attribute when not disabled', fakeAsync(() => { + fileAttachmentInstance.ngAfterViewInit(); + tick(); + fixture.detectChanges(); + const input = getInputDebugEl(fixture); + const button = getButtonEl(el); + + expect(input.nativeElement.getAttribute('disabled')).toBeNull(); + expect(button.getAttribute('disabled')).toBeNull(); + })); + + it(`should have disabled attribute when disabled input is set to true`, fakeAsync(() => { + fixture.componentInstance.disabled = true; + fileAttachmentInstance.ngAfterViewInit(); + tick(); + fixture.detectChanges(); + const input = getInputDebugEl(fixture); + const button = getButtonEl(el); + + expect(input.nativeElement.getAttribute('disabled')).not.toBeNull(); + expect(button.getAttribute('disabled')).not.toBeNull(); + + fixture.componentInstance.disabled = false; + tick(); + fixture.detectChanges(); + + expect(input.nativeElement.getAttribute('disabled')).toBeNull(); + expect(button.getAttribute('disabled')).toBeNull(); + })); +}); diff --git a/src/app/public/modules/file-attachment/file-attachment.component.ts b/src/app/public/modules/file-attachment/file-attachment.component.ts index 0b650b86..9b084dcc 100644 --- a/src/app/public/modules/file-attachment/file-attachment.component.ts +++ b/src/app/public/modules/file-attachment/file-attachment.component.ts @@ -65,6 +65,21 @@ export class SkyFileAttachmentComponent implements AfterViewInit, AfterContentIn @Input() public acceptedTypes: string; + /** + * Indicates whether to disable the input. This property accepts `boolean` values. + */ + @Input() + public set disabled(value: boolean) { + const newDisabledState = SkyFormsUtility.coerceBooleanProperty(value); + if (this._disabled !== newDisabledState) { + this._disabled = newDisabledState; + } + } + + public get disabled(): boolean { + return this._disabled; + } + @Input() public maxFileSize: number = 500000; @@ -130,6 +145,8 @@ export class SkyFileAttachmentComponent implements AfterViewInit, AfterContentIn private ngUnsubscribe = new Subject(); + private _disabled: boolean = false; + private _value: any; constructor( @@ -284,6 +301,16 @@ export class SkyFileAttachmentComponent implements AfterViewInit, AfterContentIn this.changeDetector.markForCheck(); } + /** + * @internal + * Sets the disabled state of the control. Implemented as a part of ControlValueAccessor. + * @param isDisabled Whether the control should be disabled. + */ + public setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + this.changeDetector.markForCheck(); + } + public emitClick(): void { this.fileClick.emit({ file: this.value diff --git a/src/app/public/modules/file-attachment/fixtures/file-attachment.module.fixture.ts b/src/app/public/modules/file-attachment/fixtures/file-attachment.module.fixture.ts index 88a4f3ca..6db3e413 100644 --- a/src/app/public/modules/file-attachment/fixtures/file-attachment.module.fixture.ts +++ b/src/app/public/modules/file-attachment/fixtures/file-attachment.module.fixture.ts @@ -19,9 +19,14 @@ import { SkyFileAttachmentsModule } from '../file-attachments.module'; +import { + TemplateDrivenFileAttachmentTestComponent +} from './template-driven-file-attachment.component.fixture'; + @NgModule({ declarations: [ - FileAttachmentTestComponent + FileAttachmentTestComponent, + TemplateDrivenFileAttachmentTestComponent ], imports: [ SkyFileAttachmentsModule, @@ -30,7 +35,8 @@ import { ReactiveFormsModule ], exports: [ - FileAttachmentTestComponent + FileAttachmentTestComponent, + TemplateDrivenFileAttachmentTestComponent ] }) export class FileAttachmentTestModule { } diff --git a/src/app/public/modules/file-attachment/fixtures/template-driven-file-attachment.component.fixture.ts b/src/app/public/modules/file-attachment/fixtures/template-driven-file-attachment.component.fixture.ts new file mode 100644 index 00000000..9ce903c1 --- /dev/null +++ b/src/app/public/modules/file-attachment/fixtures/template-driven-file-attachment.component.fixture.ts @@ -0,0 +1,28 @@ +import { + Component, + ViewChild +} from '@angular/core'; + +import { + SkyFileAttachmentComponent +} from '../file-attachment.component'; + +@Component({ + selector: 'file-attachment-test', + template: ` + + + Field Label + + + ` +}) +export class TemplateDrivenFileAttachmentTestComponent { + + public disabled: boolean = false; + + @ViewChild(SkyFileAttachmentComponent) + public fileAttachmentComponent: SkyFileAttachmentComponent; +} diff --git a/src/app/visual/single-file-attachment/single-file-attachment-visual.component.html b/src/app/visual/single-file-attachment/single-file-attachment-visual.component.html index d926737a..08edb78d 100644 --- a/src/app/visual/single-file-attachment/single-file-attachment-visual.component.html +++ b/src/app/visual/single-file-attachment/single-file-attachment-visual.component.html @@ -15,6 +15,13 @@ > Toggle required +

@@ -74,6 +81,7 @@

; public attachment: FormControl; + public disabled: boolean = false; + public fileForm: FormGroup; public filesToUpload: Array; @@ -63,6 +66,18 @@ export class SingleFileAttachmentVisualComponent implements OnInit { this.removeFromArray(this.filesToUpload, file); } + /** + * Toggle both the template-driven and reactive form. + */ + public onToggleDisabledClick(): void { + this.disabled = !this.disabled; + if (this.disabled) { + this.attachment.disable(); + } else { + this.attachment.enable(); + } + } + private removeFromArray(items: Array, obj: SkyFileItem | SkyFileLink): void { if (items) { const index = items.indexOf(obj);