From d8a2963e87ede053e1fd7fa4362534355e7ab8a4 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 3 Apr 2019 12:14:52 -0400 Subject: [PATCH] LP1823041 Angular dialogs return observables Dialog.open() now returns an observable to the caller. This allows dialogs to pass 0 or more success events, error events, and close events each as descrete actions to the caller. Existing dialogs are updated to expect an Observable response to .open(). Signed-off-by: Bill Erickson --- .../share/accesskey/accesskey-info.component.html | 3 +- .../src/app/share/dialog/confirm.component.html | 7 +- .../eg2/src/app/share/dialog/dialog.component.ts | 115 +++++++++++++-------- .../src/app/share/dialog/progress.component.html | 2 +- .../eg2/src/app/share/dialog/prompt.component.html | 5 +- .../app/share/fm-editor/fm-editor.component.html | 10 +- .../src/app/share/fm-editor/fm-editor.component.ts | 14 +-- .../share/grid/grid-column-config.component.html | 3 +- .../workstations/workstations.component.ts | 10 +- .../staff/cat/vandelay/match-set-list.component.ts | 12 +-- .../src/app/staff/cat/vandelay/queue.component.ts | 45 ++++---- .../record/part-merge-dialog.component.html | 5 +- .../app/staff/catalog/record/parts.component.ts | 16 +-- .../eg2/src/app/staff/sandbox/sandbox.component.ts | 13 +-- .../staff/share/admin-page/admin-page.component.ts | 36 +++---- .../buckets/record-bucket-dialog.component.html | 3 +- .../staff/share/op-change/op-change.component.html | 5 +- .../staff/share/translate/translate.component.html | 5 +- Open-ILS/src/eg2/tsconfig.json | 3 +- 19 files changed, 153 insertions(+), 159 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html index 82ed72a4b0..ae584cec40 100644 --- a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html +++ b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html @@ -2,8 +2,7 @@ diff --git a/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html index 21766cac09..3db73cc8e0 100644 --- a/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html +++ b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html @@ -2,16 +2,15 @@ diff --git a/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts index b7531a2a20..79a5c8605d 100644 --- a/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts +++ b/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts @@ -1,19 +1,32 @@ import {Component, Input, OnInit, ViewChild, TemplateRef, EventEmitter} from '@angular/core'; +import {Observable, Observer} from 'rxjs'; import {NgbModal, NgbModalRef, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; /** * Dialog base class. Handles the ngbModal logic. * Sub-classed component templates must have a #dialogContent selector * at the root of the template (see ConfirmDialogComponent). + * + * Dialogs interact with the caller via Observable. + * + * dialog.open().subscribe( + * value => handleValue(value), + * error => handleError(error), + * () => console.debug('dialog closed') + * ); + * + * It is up to the dialog implementer to decide what values to + * pass to the caller via the dialog.respond(data) and/or + * dialog.close(data) methods. + * + * dialog.close(...) closes the modal window and completes the + * observable, unless an error was previously passed, in which + * case the observable is already complete. + * + * dialog.close() with no data closes the dialog without passing + * any values to the caller. */ -export interface DialogRejectionResponse { - // Did the user simply close the dialog without performing an action. - dismissed?: boolean; - // Relays error, etc. messages from the dialog handler to the caller. - message?: string; -} - @Component({ selector: 'eg-dialog', template: '' @@ -32,6 +45,9 @@ export class DialogComponent implements OnInit { // called in the overridding method. onOpen$ = new EventEmitter(); + // How we relay responses to the caller. + observer: Observer; + // The modalRef allows direct control of the modal instance. private modalRef: NgbModalRef = null; @@ -41,11 +57,11 @@ export class DialogComponent implements OnInit { this.onOpen$ = new EventEmitter(); } - open(options?: NgbModalOptions): Promise { + open(options?: NgbModalOptions): Observable { if (this.modalRef !== null) { - console.warn('Dismissing existing dialog'); - this.dismiss(); + this.error('Dialog was replaced!'); + this.finalize(); } this.modalRef = this.modalService.open(this.dialogContent, options); @@ -55,49 +71,62 @@ export class DialogComponent implements OnInit { setTimeout(() => this.onOpen$.emit(true)); } - return new Promise( (resolve, reject) => { + return new Observable(observer => { + this.observer = observer; this.modalRef.result.then( - (result) => { - resolve(result); - this.modalRef = null; - }, - - (result) => { - // NgbModal creates some result values for us, which - // are outside of our control. Other dismissal - // reasons are agreed upon by implementing subclasses. - console.debug('dialog closed with ' + result); - - const dismissed = ( - result === 0 // body click - || result === 1 // Esc key - || result === 'canceled' // Cancel button - || result === 'cross_click' // modal top-right X - ); - - const rejection: DialogRejectionResponse = { - dismissed: dismissed, - message: result - }; - - reject(rejection); - this.modalRef = null; - } + // Results are relayed to the caller via our observer. + // Our Observer is marked complete via this.close(). + // Nothing to do here. + result => {}, + + // Modal was dismissed via UI control which + // bypasses call to this.close() + dismissed => this.finalize() ); }); } - close(reason?: any): void { - if (this.modalRef) { - this.modalRef.close(reason); + // Send a response to the caller without closing the dialog. + respond(value: any) { + if (this.observer && value !== undefined) { + this.observer.next(value); + } + } + + // Sends error event to the caller and closes the dialog. + // Once an error is sent, our observable is complete and + // cannot be used again to send any messages. + error(value: any, close?: boolean) { + if (this.observer) { + console.error('Dialog produced error', value); + this.observer.error(value); + this.observer = null; } + if (this.modalRef) { this.modalRef.close(); } + this.finalize(); + } + + // Close the dialog, optionally with a value to relay to the caller. + // Calling close() with no value simply dismisses the dialog. + close(value?: any) { + this.respond(value); + if (this.modalRef) { this.modalRef.close(); } + this.finalize(); + } + + dismiss() { + console.warn('Dialog.dismiss() is deprecated. Use close() instead'); + this.close(); } - dismiss(reason?: any): void { - if (this.modalRef) { - this.modalRef.dismiss(reason); + // Clean up after closing the dialog. + finalize() { + if (this.observer) { // null if this.error() called + this.observer.complete(); + this.observer = null; } + this.modalRef = null; } } diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html index 78ca3d0c95..c1fdf20c0f 100644 --- a/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html @@ -3,7 +3,7 @@ diff --git a/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html index 1d7936b176..17a6b50726 100644 --- a/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html +++ b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html @@ -2,8 +2,7 @@ @@ -17,6 +16,6 @@ + (click)="close()" i18n>Cancel diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html index aad65d15d6..2a6884186d 100644 --- a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html @@ -2,13 +2,18 @@ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts index 3ab8e8f8e9..2f59374084 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts @@ -81,10 +81,8 @@ export class PartsComponent implements OnInit { (part: IdlObject) => { this.editDialog.mode = 'update'; this.editDialog.recId = part.id(); - this.editDialog.open().then( - ok => this.partsGrid.reload(), - err => {} - ); + this.editDialog.open() + .subscribe(ok => this.partsGrid.reload()); } ); @@ -95,10 +93,7 @@ export class PartsComponent implements OnInit { this.editDialog.record = part; this.editDialog.mode = 'create'; - this.editDialog.open().then( - ok => this.partsGrid.reload(), - err => {} - ); + this.editDialog.open().subscribe(ok => this.partsGrid.reload()); }; this.deleteSelected = (parts: IdlObject[]) => { @@ -113,10 +108,7 @@ export class PartsComponent implements OnInit { this.mergeSelected = (parts: IdlObject[]) => { if (parts.length < 2) { return; } this.mergeDialog.parts = parts; - this.mergeDialog.open().then( - ok => this.partsGrid.reload(), - err => console.debug('Dialog dismissed') - ); + this.mergeDialog.open().subscribe(ok => this.partsGrid.reload()); }; } } diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts index 9b058cd5b1..9579e9577e 100644 --- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts @@ -137,15 +137,10 @@ export class SandboxComponent implements OnInit { } openEditor() { - this.fmRecordEditor.open({size: 'lg'}).then( - ok => { console.debug(ok); }, - err => { - if (err && err.dismissed) { - console.debug('dialog was dismissed'); - } else { - console.error(err); - } - } + this.fmRecordEditor.open({size: 'lg'}).subscribe( + pcrudResult => console.debug('Record editor performed action'), + err => console.error(err), + () => console.debug('Dialog closed') ); } diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts index 125d3a0fd2..ad509c8316 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts @@ -178,17 +178,15 @@ export class AdminPageComponent implements OnInit { // create action does not try to modify an existing record. this.editDialog.recId = null; this.editDialog.record = null; - this.editDialog.open({size: this.dialogSize}).then( - ok => { + this.editDialog.open({size: this.dialogSize}).subscribe( + result => { this.createString.current() .then(str => this.toast.success(str)); this.grid.reload(); }, - rejection => { - if (!rejection.dismissed) { - this.createErrString.current() - .then(str => this.toast.danger(str)); - } + error => { + this.createErrString.current() + .then(str => this.toast.danger(str)); } ); }; @@ -325,22 +323,24 @@ export class AdminPageComponent implements OnInit { return this.contextOrg && this.contextOrg.children().length === 0; } - showEditDialog(idlThing: IdlObject) { + showEditDialog(idlThing: IdlObject): Promise { this.editDialog.mode = 'update'; this.editDialog.recId = idlThing[this.pkeyField](); - return this.editDialog.open({size: this.dialogSize}).then( - ok => { - this.successString.current() - .then(str => this.toast.success(str)); - this.grid.reload(); - }, - rejection => { - if (!rejection.dismissed) { + return new Promise((resolve, reject) => { + this.editDialog.open({size: this.dialogSize}).subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + this.grid.reload(); + resolve(result); + }, + error => { this.updateFailedString.current() .then(str => this.toast.danger(str)); + reject(error); } - } - ); + ); + }); } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html index a2c88b8e34..27eea66130 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html @@ -10,8 +10,7 @@ Add Records from queue #{{qId}} to Bucket diff --git a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html index e5a6f493b5..d472202999 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html @@ -2,8 +2,7 @@ @@ -60,6 +59,6 @@ diff --git a/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html index 7aa59b46c3..61b9cb4f90 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html @@ -4,8 +4,7 @@ {{idlClassDef.label}} @@ -58,6 +57,6 @@ - + diff --git a/Open-ILS/src/eg2/tsconfig.json b/Open-ILS/src/eg2/tsconfig.json index 14a504dc91..157d6e6722 100644 --- a/Open-ILS/src/eg2/tsconfig.json +++ b/Open-ILS/src/eg2/tsconfig.json @@ -18,7 +18,8 @@ ], "lib": [ "es2017", - "dom" + "dom", + "es2018.promise" ] } } -- 2.11.0