From: Stephan Müller Date: Mon, 16 Apr 2018 11:44:38 +0000 (+0200) Subject: mgr/dashboard: Change deletion link to modal only X-Git-Tag: v13.1.0~126^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=63ae858582ec48487e28acf59a4dc315bd423303;p=ceph.git mgr/dashboard: Change deletion link to modal only Due to CSS problems the link solution wasn't the best way, now it will represent only the modal content. The downside of this solution is that it put's the burden on the developer to use it the right way and import a view things to get it working. But on the upside CSS styles will work as expected. The unit test example was updated accordingly this way it should be easy to understand how it can be implemented the right way. Signed-off-by: Stephan Müller --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index cc3dd5bbfebe..63840d6f2bb9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -9,7 +9,7 @@ import { PipesModule } from '../pipes/pipes.module'; import { DeleteConfirmationComponent } from './delete-confirmation-modal/delete-confirmation-modal.component'; -import { DeletionLinkComponent } from './deletion-link/deletion-link.component'; +import { DeletionModalComponent } from './deletion-modal/deletion-modal.component'; import { HelperComponent } from './helper/helper.component'; import { ModalComponent } from './modal/modal.component'; import { SparklineComponent } from './sparkline/sparkline.component'; @@ -38,7 +38,10 @@ import { ViewCacheComponent } from './view-cache/view-cache.component'; UsageBarComponent, DeleteConfirmationComponent, ModalComponent, - DeletionLinkComponent + DeletionModalComponent + ], + entryComponents: [ + DeletionModalComponent ], providers: [], exports: [ @@ -52,7 +55,7 @@ import { ViewCacheComponent } from './view-cache/view-cache.component'; entryComponents: [ DeleteConfirmationComponent, ModalComponent, - DeletionLinkComponent + DeletionModalComponent ] }) export class ComponentsModule { } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.html deleted file mode 100644 index 9910fe9d7b3c..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - Delete - - {{ metaType }} - - - - - - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.scss deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.spec.ts deleted file mode 100644 index 9ee3f608ba0f..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { FormGroupDirective, ReactiveFormsModule } from '@angular/forms'; - -import { ModalModule } from 'ngx-bootstrap'; -import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; - -import { ModalComponent } from '../modal/modal.component'; -import { SubmitButtonComponent } from '../submit-button/submit-button.component'; -import { DeletionLinkComponent } from './deletion-link.component'; - -@Component({ - template: ` - - The spinner is handled by the controller if you have use the modal as ViewChild in order to - use it's functions to stop the spinner or close the dialog. - - - The spinner is handled by the modal if your given deletion function returns a Observable. - - ` -}) -class MockComponent { - @ViewChild('ctrlDeleteButton') ctrlDeleteButton: DeletionLinkComponent; - @ViewChild('modalDeleteButton') modalDeleteButton: DeletionLinkComponent; - someData = [1, 2, 3, 4, 5]; - finished: number[]; - - finish() { - this.finished = [6, 7, 8, 9]; - } - - fakeDelete() { - return (): Observable => { - return new Observable((observer: Subscriber) => { - Observable.timer(100).subscribe(() => { - observer.next(this.finish()); - observer.complete(); - }); - }); - }; - } - - fakeDeleteController() { - Observable.timer(100).subscribe(() => { - this.finish(); - this.ctrlDeleteButton.hideModal(); - }); - } -} - -describe('DeletionLinkComponent', () => { - let mockComponent: MockComponent; - let component: DeletionLinkComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MockComponent, DeletionLinkComponent, ModalComponent, - SubmitButtonComponent], - imports: [ModalModule.forRoot(), ReactiveFormsModule], - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MockComponent); - mockComponent = fixture.componentInstance; - component = mockComponent.ctrlDeleteButton; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('component functions', () => { - - const mockShowModal = () => { - component.showModal(null); - }; - - const changeValue = (value) => { - component.confirmation.setValue(value); - component.confirmation.markAsDirty(); - component.confirmation.updateValueAndValidity(); - }; - - beforeEach(() => { - spyOn(component.modalService, 'show').and.returnValue({ - hide: () => true - }); - }); - - it('should test showModal', () => { - changeValue('something'); - expect(mockShowModal).toBeTruthy(); - expect(component.confirmation.value).toBe('something'); - expect(component.modalService.show).not.toHaveBeenCalled(); - mockShowModal(); - expect(component.modalService.show).toHaveBeenCalled(); - expect(component.confirmation.value).toBe(null); - expect(component.confirmation.pristine).toBe(true); - }); - - it('should test hideModal', () => { - expect(component.bsModalRef).not.toBeTruthy(); - mockShowModal(); - expect(component.bsModalRef).toBeTruthy(); - expect(component.hideModal).toBeTruthy(); - spyOn(component.bsModalRef, 'hide').and.stub(); - expect(component.bsModalRef.hide).not.toHaveBeenCalled(); - component.hideModal(); - expect(component.bsModalRef.hide).toHaveBeenCalled(); - }); - - describe('invalid control', () => { - - const testInvalidControl = (submitted: boolean, error: string, expected: boolean) => { - expect(component.invalidControl(submitted, error)).toBe(expected); - }; - - beforeEach(() => { - component.deletionForm.reset(); - }); - - it('should test empty values', () => { - expect(component.invalidControl).toBeTruthy(); - component.deletionForm.reset(); - testInvalidControl(false, undefined, false); - testInvalidControl(true, 'required', true); - component.deletionForm.reset(); - changeValue('let-me-pass'); - changeValue(''); - testInvalidControl(true, 'required', true); - }); - - it('should test pattern', () => { - changeValue('let-me-pass'); - testInvalidControl(false, 'pattern', true); - changeValue('ctrl-test'); - testInvalidControl(false, undefined, false); - testInvalidControl(true, undefined, false); - }); - }); - - describe('deletion call', () => { - beforeEach(() => { - spyOn(component.toggleDeletion, 'emit'); - spyOn(component, 'stopLoadingSpinner'); - spyOn(component, 'hideModal').and.stub(); - }); - - describe('Controller driven', () => { - beforeEach(() => { - mockShowModal(); - expect(component.toggleDeletion.emit).not.toHaveBeenCalled(); - expect(component.stopLoadingSpinner).not.toHaveBeenCalled(); - expect(component.hideModal).not.toHaveBeenCalled(); - }); - - it('should delete without doing anything but call emit', () => { - component.deletionCall(); - expect(component.stopLoadingSpinner).not.toHaveBeenCalled(); - expect(component.hideModal).not.toHaveBeenCalled(); - expect(component.toggleDeletion.emit).toHaveBeenCalled(); - }); - - it('should test fake deletion that closes modal', fakeAsync(() => { - mockComponent.fakeDeleteController(); - expect(component.hideModal).not.toHaveBeenCalled(); - expect(mockComponent.finished).toBe(undefined); - tick(2000); - expect(component.hideModal).toHaveBeenCalled(); - expect(mockComponent.finished).toEqual([6, 7, 8, 9]); - })); - }); - - describe('Modal driven', () => { - it('should delete and close modal', fakeAsync(() => { - component = mockComponent.modalDeleteButton; - mockShowModal(); - spyOn(component.toggleDeletion, 'emit'); - spyOn(component, 'stopLoadingSpinner'); - spyOn(component, 'hideModal').and.stub(); - spyOn(mockComponent, 'fakeDelete'); - - component.deletionCall(); - expect(mockComponent.finished).toBe(undefined); - expect(component.toggleDeletion.emit).not.toHaveBeenCalled(); - expect(component.hideModal).not.toHaveBeenCalled(); - - tick(2000); - expect(component.toggleDeletion.emit).not.toHaveBeenCalled(); - expect(component.stopLoadingSpinner).not.toHaveBeenCalled(); - expect(component.hideModal).toHaveBeenCalled(); - expect(mockComponent.finished).toEqual([6, 7, 8, 9]); - })); - }); - }); - }); - -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.ts deleted file mode 100644 index cd79da24fc0d..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-link/deletion-link.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - Component, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild -} from '@angular/core'; -import { FormControl, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; - -import { BsModalRef, BsModalService } from 'ngx-bootstrap'; -import { Observable } from 'rxjs/Observable'; - -import { SubmitButtonComponent } from '../submit-button/submit-button.component'; - -@Component({ - selector: 'cd-deletion-link', - templateUrl: './deletion-link.component.html', - styleUrls: ['./deletion-link.component.scss'] -}) -export class DeletionLinkComponent implements OnInit { - @ViewChild(SubmitButtonComponent) submitButton: SubmitButtonComponent; - @Input() metaType: string; - @Input() pattern = 'yes'; - @Input() deletionObserver: () => Observable; - @Output() toggleDeletion = new EventEmitter(); - bsModalRef: BsModalRef; - deletionForm: FormGroup; - confirmation: FormControl; - delete: Function; - - constructor(public modalService: BsModalService) {} - - ngOnInit() { - this.confirmation = new FormControl('', { - validators: [ - Validators.required, - Validators.pattern(this.pattern) - ], - updateOn: 'blur' - }); - this.deletionForm = new FormGroup({ - confirmation: this.confirmation - }); - } - - showModal(template: TemplateRef) { - this.deletionForm.reset(); - this.bsModalRef = this.modalService.show(template); - this.delete = () => { - this.submitButton.submit(); - }; - } - - invalidControl(submitted: boolean, error?: string): boolean { - const control = this.confirmation; - return !!( - (submitted || control.dirty) && - control.invalid && - (error ? control.errors[error] : true) - ); - } - - updateConfirmation($e) { - if ($e.key !== 'Enter') { - return; - } - this.confirmation.setValue($e.target.value); - this.confirmation.markAsDirty(); - this.confirmation.updateValueAndValidity(); - } - - deletionCall() { - if (this.deletionObserver) { - this.deletionObserver().subscribe( - undefined, - () => this.stopLoadingSpinner(), - () => this.hideModal() - ); - } else { - this.toggleDeletion.emit(); - } - } - - hideModal() { - this.bsModalRef.hide(); - } - - stopLoadingSpinner() { - this.submitButton.loading = false; - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html new file mode 100644 index 000000000000..59132d457eba --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html @@ -0,0 +1,73 @@ + + + + + + +
+ + +
+
+
+ + + + Delete + + {{ metaType }} + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts new file mode 100644 index 000000000000..bce74a2be3fc --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts @@ -0,0 +1,342 @@ +import { Component, NgModule, TemplateRef, ViewChild } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap'; +import { Observable } from 'rxjs/Observable'; +import { Subscriber } from 'rxjs/Subscriber'; + +import { ModalComponent } from '../modal/modal.component'; +import { SubmitButtonComponent } from '../submit-button/submit-button.component'; +import { DeletionModalComponent } from './deletion-modal.component'; + +@NgModule({ + entryComponents: [DeletionModalComponent] +}) +export class MockModule {} + +@Component({ + template: ` + + + + ` +}) +class MockComponent { + @ViewChild('ctrlDescription') ctrlDescription: TemplateRef; + @ViewChild('modalDescription') modalDescription: TemplateRef; + someData = [1, 2, 3, 4, 5]; + finished: number[]; + ctrlRef: BsModalRef; + modalRef: BsModalRef; + + // Normally private - public was needed for the tests + constructor(public modalService: BsModalService) {} + + openCtrlDriven() { + this.ctrlRef = this.modalService.show(DeletionModalComponent); + this.ctrlRef.content.setUp({ + metaType: 'Controller delete handling', + pattern: 'ctrl-test', + deletionMethod: this.fakeDeleteController.bind(this), + description: this.ctrlDescription, + modalRef: this.ctrlRef + }); + } + + openModalDriven() { + this.modalRef = this.modalService.show(DeletionModalComponent); + this.modalRef.content.setUp({ + metaType: 'Modal delete handling', + pattern: 'modal-test', + deletionObserver: this.fakeDelete(), + description: this.modalDescription, + modalRef: this.modalRef + }); + } + + finish() { + this.finished = [6, 7, 8, 9]; + } + + fakeDelete() { + return (): Observable => { + return new Observable((observer: Subscriber) => { + Observable.timer(100).subscribe(() => { + observer.next(this.finish()); + observer.complete(); + }); + }); + }; + } + + fakeDeleteController() { + Observable.timer(100).subscribe(() => { + this.finish(); + this.ctrlRef.hide(); + }); + } +} + +describe('DeletionModalComponent', () => { + let mockComponent: MockComponent; + let component: DeletionModalComponent; + let mockFixture: ComponentFixture; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MockComponent, DeletionModalComponent, ModalComponent, + SubmitButtonComponent], + imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule], + }) + .compileComponents(); + })); + + beforeEach(() => { + mockFixture = TestBed.createComponent(MockComponent); + mockComponent = mockFixture.componentInstance; + // Mocking the modals as a lot would be left over + spyOn(mockComponent.modalService, 'show').and.callFake(() => { + const ref = new BsModalRef(); + fixture = TestBed.createComponent(DeletionModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + ref.content = component; + return ref; + }); + mockComponent.openCtrlDriven(); + mockFixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('setUp', () => { + const clearSetup = () => { + component.metaType = undefined; + component.pattern = 'yes'; + component.deletionObserver = undefined; + component.description = undefined; + component.modalRef = undefined; + }; + + const expectSetup = (metaType, observer: boolean, method: boolean, pattern, + template: boolean) => { + expect(component.modalRef).toBeTruthy(); + expect(component.metaType).toBe(metaType); + expect(!!component.deletionObserver).toBe(observer); + expect(!!component.deletionMethod).toBe(method); + expect(component.pattern).toBe(pattern); + expect(!!component.description).toBe(template); + }; + + beforeEach(() => { + clearSetup(); + }); + + it('should throw error if no modal reference is given', () => { + expect(() => component.setUp({ + metaType: undefined, + modalRef: undefined + })).toThrowError('No modal reference'); + }); + + it('should throw error if no meta type is given', () => { + expect(() => component.setUp({ + metaType: undefined, + modalRef: mockComponent.ctrlRef + })).toThrowError('No meta type'); + }); + + it('should throw error if no deletion method is given', () => { + expect(() => component.setUp({ + metaType: 'Sth', + modalRef: mockComponent.ctrlRef + })).toThrowError('No deletion method'); + }); + + it('should throw no errors if metaType, modalRef and a deletion method were given', + () => { + component.setUp({ + metaType: 'Observer', + modalRef: mockComponent.ctrlRef, + deletionObserver: mockComponent.fakeDelete() + }); + expectSetup('Observer', true, false, 'yes', false); + clearSetup(); + component.setUp({ + metaType: 'Controller', + modalRef: mockComponent.ctrlRef, + deletionMethod: mockComponent.fakeDeleteController + }); + expectSetup('Controller', false, true, 'yes', false); + }); + + it('should test optional parameters - pattern and description', + () => { + component.setUp({ + metaType: 'Pattern only', + modalRef: mockComponent.ctrlRef, + deletionObserver: mockComponent.fakeDelete(), + pattern: '{sth/!$_8()' + }); + expectSetup('Pattern only', true, false, '{sth/!$_8()', false); + clearSetup(); + component.setUp({ + metaType: 'Description only', + modalRef: mockComponent.ctrlRef, + deletionObserver: mockComponent.fakeDelete(), + description: mockComponent.modalDescription + }); + expectSetup('Description only', true, false, 'yes', true); + clearSetup(); + component.setUp({ + metaType: 'Description and pattern', + modalRef: mockComponent.ctrlRef, + deletionObserver: mockComponent.fakeDelete(), + description: mockComponent.modalDescription, + pattern: '{sth/!$_8()' + }); + expectSetup('Description and pattern', true, false, '{sth/!$_8()', true); + }); + }); + + it('should test if the ctrl driven mock is set correctly through mock component', () => { + expect(component.metaType).toBe('Controller delete handling'); + expect(component.pattern).toBe('ctrl-test'); + expect(component.description).toBeTruthy(); + expect(component.modalRef).toBeTruthy(); + expect(component.deletionMethod).toBeTruthy(); + expect(component.deletionObserver).not.toBeTruthy(); + }); + + it('should test if the modal driven mock is set correctly through mock component', () => { + mockComponent.openModalDriven(); + expect(component.metaType).toBe('Modal delete handling'); + expect(component.pattern).toBe('modal-test'); + expect(component.description).toBeTruthy(); + expect(component.modalRef).toBeTruthy(); + expect(component.deletionObserver).toBeTruthy(); + expect(component.deletionMethod).not.toBeTruthy(); + }); + + describe('component functions', () => { + const changeValue = (value) => { + component.confirmation.setValue(value); + component.confirmation.markAsDirty(); + component.confirmation.updateValueAndValidity(); + fixture.detectChanges(); + }; + + it('should test hideModal', () => { + expect(component.modalRef).toBeTruthy(); + expect(component.hideModal).toBeTruthy(); + spyOn(component.modalRef, 'hide').and.callThrough(); + expect(component.modalRef.hide).not.toHaveBeenCalled(); + component.hideModal(); + expect(component.modalRef.hide).toHaveBeenCalled(); + }); + + describe('invalid control', () => { + const testInvalidControl = (submitted: boolean, error: string, expected: boolean) => { + expect(component.invalidControl(submitted, error)).toBe(expected); + }; + + beforeEach(() => { + component.deletionForm.reset(); + }); + + it('should test empty values', () => { + expect(component.invalidControl).toBeTruthy(); + component.deletionForm.reset(); + testInvalidControl(false, undefined, false); + testInvalidControl(true, 'required', true); + component.deletionForm.reset(); + changeValue('let-me-pass'); + changeValue(''); + testInvalidControl(true, 'required', true); + }); + + it('should test pattern', () => { + changeValue('let-me-pass'); + testInvalidControl(false, 'pattern', true); + changeValue('ctrl-test'); + testInvalidControl(false, undefined, false); + testInvalidControl(true, undefined, false); + }); + }); + + describe('deletion call', () => { + beforeEach(() => { + spyOn(component, 'stopLoadingSpinner').and.callThrough(); + spyOn(component, 'hideModal').and.callThrough(); + }); + + describe('Controller driven', () => { + beforeEach(() => { + spyOn(component, 'deletionMethod').and.callThrough(); + spyOn(mockComponent.ctrlRef, 'hide').and.callThrough(); + }); + + it('should test fake deletion that closes modal', fakeAsync(() => { + // Before deletionCall + expect(component.deletionMethod).not.toHaveBeenCalled(); + // During deletionCall + component.deletionCall(); + expect(component.stopLoadingSpinner).not.toHaveBeenCalled(); + expect(component.hideModal).not.toHaveBeenCalled(); + expect(mockComponent.ctrlRef.hide).not.toHaveBeenCalled(); + expect(component.deletionMethod).toHaveBeenCalled(); + expect(mockComponent.finished).toBe(undefined); + // After deletionCall + tick(2000); + expect(component.hideModal).not.toHaveBeenCalled(); + expect(mockComponent.ctrlRef.hide).toHaveBeenCalled(); + expect(mockComponent.finished).toEqual([6, 7, 8, 9]); + })); + }); + + describe('Modal driven', () => { + beforeEach(() => { + mockComponent.openModalDriven(); + spyOn(mockComponent.modalRef, 'hide').and.callThrough(); + spyOn(component, 'stopLoadingSpinner').and.callThrough(); + spyOn(component, 'hideModal').and.callThrough(); + spyOn(mockComponent, 'fakeDelete').and.callThrough(); + }); + + it('should delete and close modal', fakeAsync(() => { + // During deletionCall + component.deletionCall(); + expect(mockComponent.finished).toBe(undefined); + expect(component.hideModal).not.toHaveBeenCalled(); + expect(mockComponent.modalRef.hide).not.toHaveBeenCalled(); + // After deletionCall + tick(2000); + expect(mockComponent.finished).toEqual([6, 7, 8, 9]); + expect(mockComponent.modalRef.hide).toHaveBeenCalled(); + expect(component.stopLoadingSpinner).not.toHaveBeenCalled(); + expect(component.hideModal).toHaveBeenCalled(); + })); + }); + }); + }); + +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts new file mode 100644 index 000000000000..d27963cfe5f4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts @@ -0,0 +1,100 @@ +import { + Component, OnInit, TemplateRef, ViewChild +} from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +import { BsModalRef, BsModalService } from 'ngx-bootstrap'; +import { Observable } from 'rxjs/Observable'; + +import { SubmitButtonComponent } from '../submit-button/submit-button.component'; + +@Component({ + selector: 'cd-deletion-modal', + templateUrl: './deletion-modal.component.html', + styleUrls: ['./deletion-modal.component.scss'] +}) +export class DeletionModalComponent implements OnInit { + @ViewChild(SubmitButtonComponent) submitButton: SubmitButtonComponent; + description: TemplateRef; + metaType: string; + pattern = 'yes'; + deletionObserver: () => Observable; + deletionMethod: Function; + modalRef: BsModalRef; + + deletionForm: FormGroup; + confirmation: FormControl; + + // Parameters are destructed here than assigned to specific types and marked as optional + setUp({modalRef, metaType, deletionMethod, pattern, deletionObserver, description}: + { modalRef: BsModalRef, metaType: string, deletionMethod?: Function, pattern?: string, + deletionObserver?: () => Observable, description?: TemplateRef}) { + if (!modalRef) { + throw new Error('No modal reference'); + } else if (!metaType) { + throw new Error('No meta type'); + } else if (!(deletionMethod || deletionObserver)) { + throw new Error('No deletion method'); + } + this.metaType = metaType; + this.modalRef = modalRef; + this.deletionMethod = deletionMethod; + this.pattern = pattern || this.pattern; + this.deletionObserver = deletionObserver; + this.description = description; + } + + ngOnInit() { + this.confirmation = new FormControl('', { + validators: [ + Validators.required + ], + updateOn: 'blur' + }); + this.deletionForm = new FormGroup({ + confirmation: this.confirmation + }); + } + + invalidControl(submitted: boolean, error?: string): boolean { + const control = this.confirmation; + return !!( + (submitted || control.dirty) && + control.invalid && + (error ? control.errors[error] : true) + ); + } + + updateConfirmation($e) { + if ($e.key !== 'Enter') { + return; + } + this.confirmation.setValue($e.target.value); + this.confirmation.markAsDirty(); + this.confirmation.updateValueAndValidity(); + } + + delete () { + this.submitButton.submit(); + } + + deletionCall() { + if (this.deletionObserver) { + this.deletionObserver().subscribe( + undefined, + this.stopLoadingSpinner.bind(this), + this.hideModal.bind(this) + ); + } else { + this.deletionMethod(); + } + } + + hideModal() { + this.modalRef.hide(); + } + + stopLoadingSpinner() { + this.submitButton.loading = false; + } +}