LP1840050 FM Editor refresh display for inline mode + more
authorBill Erickson <berickxx@gmail.com>
Mon, 12 Aug 2019 20:44:14 +0000 (16:44 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 16 Aug 2019 20:33:32 +0000 (16:33 -0400)
Force the editor to re-render its record in real time when using inline
mode, since the call to open() will never occur for inline mode.

Pass the org unit ID to org selects as they change, instead of only at
startup time, since the org select component may be rendered (in inline
mode) before the org ID is available to apply as a startOrgId value.

Migrate toward set/get functions for updating records and recordIds and
deprecate the redundant setRecord() function.

Adds a new hideBanner @Input() which prevents the modal header from
displaying, typically used for 'inline' mode.

Improve @Output() names and update refs.

IDL services gets a pkeyMatches method for testing IDLObject equality.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/core/idl.service.ts
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-list.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/record/parts.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts

index 56b8b90..0537f05 100644 (file)
@@ -156,5 +156,14 @@ export class IdlService {
         }
         return null;
     }
+
+    // Returns true if both objects have the same IDL class and pkey value.
+    pkeyMatches(obj1: IdlObject, obj2: IdlObject) {
+        if (!obj1 || !obj2) { return false; }
+        const idlClass = obj1.classname;
+        if (idlClass !== obj2.classname) { return false; }
+        const pkeyField = this.classes[idlClass].pkey || 'id';
+        return obj1[pkeyField]() === obj2[pkeyField]();
+    }
 }
 
index fc11eee..2621b3e 100644 (file)
@@ -5,7 +5,7 @@
 <eg-string #failStr text="Update Failed" i18n-text></eg-string>
 
 <ng-template #dialogContent>
-  <div class="modal-header bg-info">
+  <div class="modal-header bg-info" *ngIf="!hideBanner">
     <h4 class="modal-title" i18n>Record Editor: {{recordLabel}}</h4>
     <ng-container *ngIf="isDialog()">
       <button type="button" class="close" 
@@ -67,7 +67,7 @@
                 [limitPerms]="modePerms[mode]"
                 [readOnly]="field.readOnly"
                 [applyDefault]="field.orgDefaultAllowed"
-                [initialOrgId]="record[field.name]()"
+                [applyOrgId]="record[field.name]()"
                 (onChange)="record[field.name]($event)">
               </eg-org-select>
             </ng-container>
index 9753ce5..33da1f4 100644 (file)
@@ -82,11 +82,6 @@ export class FmRecordEditorComponent
     // IDL class hint (e.g. "aou")
     @Input() idlClass: string;
 
-    recId: any;
-
-    // IDL record we are editing
-    record: IdlObject;
-
     // Permissions extracted from the permacrud defs in the IDL
     // for the current IDL class
     modePerms: {[mode: string]: string};
@@ -123,14 +118,18 @@ export class FmRecordEditorComponent
     // Display within a modal dialog window or inline in the page.
     @Input() displayMode: 'dialog' | 'inline' = 'dialog';
 
+    // Hide the top 'Record Editor: ...' banner.  Primarily useful
+    // for displayMode === 'inline'
+    @Input() hideBanner: boolean;
+
     // Emit the modified object when the save action completes.
-    @Output() onSave$ = new EventEmitter<IdlObject>();
+    @Output() recordSaved = new EventEmitter<IdlObject>();
 
     // Emit the original object when the save action is canceled.
-    @Output() onCancel$ = new EventEmitter<IdlObject>();
+    @Output() recordCanceled = new EventEmitter<IdlObject>();
 
     // Emit an error message when the save action fails.
-    @Output() onError$ = new EventEmitter<string>();
+    @Output() recordError = new EventEmitter<string>();
 
     @ViewChild('translator') private translator: TranslateComponent;
     @ViewChild('successStr') successStr: StringComponent;
@@ -154,12 +153,50 @@ export class FmRecordEditorComponent
     //       'view' for viewing an existing record without editing
     @Input() mode: 'create' | 'update' | 'view' = 'create';
 
-    // Record ID to view/update.  Value is dynamic.  Records are not
-    // fetched until .open() is called.
+    // recordId and record getters and setters.
+    // Note that setting the this.recordId to NULL does not clear the
+    // current value of this.record and vice versa.  Only viable data
+    // is actionable.  This allows the caller to use both @Input()'s
+    // without each clobbering the other.
+
+    // Record ID to view/update.  
+    _recordId: any = null;
     @Input() set recordId(id: any) {
-        if (id) { this.recId = id; }
+        if (id) {
+            if (id !== this._recordId) {
+                this._recordId = id;
+                this._record = null; // force re-fetch
+                this.handleRecordChange();
+            }
+        } else {
+            this._recordId = null;
+        }
+    }
+
+    get recordId(): any {
+        return this._recordId;
+    }
+
+    // IDL record we are editing
+    _record: IdlObject = null;
+    @Input() set record(r: IdlObject) {
+        if (r) {
+            if (!this.idl.pkeyMatches(this.record, r)) {
+                this._record = r;
+                this._recordId = null; // avoid mismatch
+                this.handleRecordChange();
+            }
+        } else {
+            this._record = null;
+        }
     }
 
+    get record(): IdlObject {
+        return this._record;
+    }
+
+    initDone: boolean;
+
     // Comma-separated list of field names defining the order in which
     // fields should be rendered in the form.  Any fields not represented
     // will be rendered alphabetically by label after the named fields.
@@ -189,17 +226,27 @@ export class FmRecordEditorComponent
         } else {
             this.initRecord();
         }
+        this.initDone = true;
+    }
+
+    // If the record ID changes after ngOnInit has been called
+    // and we're using displayMode=inline, force the data to
+    // resync in real time
+    handleRecordChange() {
+        if (this.initDone && !this.isDialog()) {
+            this.initRecord();
+        }
     }
 
     isDialog(): boolean {
         return this.displayMode === 'dialog';
     }
 
-    // Set the record value and clear the recId value to
-    // indicate the record is our current source of data.
+    // DEPRECATED: This is a duplicate of this.record = abc;
     setRecord(record: IdlObject) {
-        this.record = record;
-        this.recId = null;
+        console.warn('fm-editor:setRecord() is deprecated. ' +
+            'Use editor.record = abc or [record]="abc" instead');
+        this.record = record; // this calls the setter
     }
 
     // Translate comma-separated string versions of various inputs
@@ -233,21 +280,25 @@ export class FmRecordEditorComponent
         if (this.mode === 'update' || this.mode === 'view') {
 
             let promise;
-            if (this.record && this.recId === null) {
+            if (this.record && this.recordId === null) {
                 promise = Promise.resolve(this.record);
-            } else {
+            } else if (this.recordId) {
                 promise =
-                    this.pcrud.retrieve(this.idlClass, this.recId).toPromise();
+                    this.pcrud.retrieve(this.idlClass, this.recordId).toPromise();
+            } else {
+                // Not enough data yet to fetch anything
+                return Promise.resolve();
             }
 
             return promise.then(rec => {
 
                 if (!rec) {
                     return Promise.reject(`No '${this.idlClass}'
-                        record found with id ${this.recId}`);
+                        record found with id ${this.recordId}`);
                 }
 
-                this.record = rec;
+                // Set this._record (not this.record) to avoid loop in initRecord()
+                this._record = rec;
                 this.convertDatatypesToJs();
                 return this.getFieldList();
             });
@@ -257,7 +308,9 @@ export class FmRecordEditorComponent
         //
         // Create a new record from the stub record provided by the
         // caller or a new from-scratch record
-        this.setRecord(this.record || this.idl.create(this.idlClass));
+        // Set this._record (not this.record) to avoid loop in initRecord()
+        this._record = this.record || this.idl.create(this.idlClass);
+        this._recordId = null; // avoid future confusion
 
         return this.getFieldList();
     }
@@ -501,12 +554,12 @@ export class FmRecordEditorComponent
         this.convertDatatypesToIdl(recToSave);
         this.pcrud[this.mode]([recToSave]).toPromise().then(
             result => {
-                this.onSave$.emit(result);
+                this.recordSaved.emit(result);
                 this.successStr.current().then(msg => this.toast.success(msg));
                 if (this.isDialog()) { this.close(result); }
             },
             error => {
-                this.onError$.emit(error);
+                this.recordError.emit(error);
                 this.failStr.current().then(msg => this.toast.warning(msg));
                 if (this.isDialog()) { this.error(error); }
             }
@@ -514,7 +567,7 @@ export class FmRecordEditorComponent
     }
 
     cancel() {
-        this.onCancel$.emit(this.record);
+        this.recordCanceled.emit(this.record);
         this.close();
     }
 
index c33999a..8efe6b5 100644 (file)
@@ -59,7 +59,7 @@ export class MatchSetListComponent implements AfterViewInit {
         this.grid.onRowActivate.subscribe(
             (matchSet: IdlObject) => {
                 this.editDialog.mode = 'update';
-                this.editDialog.recId = matchSet.id();
+                this.editDialog.recordId = matchSet.id();
                 this.editDialog.open({size: 'lg'})
                     .subscribe(() => this.grid.reload());
             }
index 2f59374..9766c7f 100644 (file)
@@ -80,7 +80,7 @@ export class PartsComponent implements OnInit {
         this.partsGrid.onRowActivate.subscribe(
             (part: IdlObject) => {
                 this.editDialog.mode = 'update';
-                this.editDialog.recId = part.id();
+                this.editDialog.recordId = part.id();
                 this.editDialog.open()
                     .subscribe(ok => this.partsGrid.reload());
             }
@@ -89,7 +89,7 @@ export class PartsComponent implements OnInit {
         this.createNew = () => {
 
             const part = this.idl.create('bmp');
-            part.record(this.recId);
+            part.record(this.recordId);
             this.editDialog.record = part;
 
             this.editDialog.mode = 'create';
index 740d4d1..efbca92 100644 (file)
@@ -365,7 +365,7 @@ export class SandboxComponent implements OnInit {
 
     showEditDialog(idlThing: IdlObject): Promise<any> {
         this.editDialog.mode = 'update';
-        this.editDialog.recId = idlThing['id']();
+        this.editDialog.recordId = idlThing['id']();
         return new Promise((resolve, reject) => {
             this.editDialog.open({size: 'lg'}).subscribe(
                 ok => {
index f920d7b..c7798c4 100644 (file)
@@ -240,7 +240,7 @@ export class AdminPageComponent implements OnInit {
 
     showEditDialog(idlThing: IdlObject): Promise<any> {
         this.editDialog.mode = 'update';
-        this.editDialog.recId = idlThing[this.pkeyField]();
+        this.editDialog.recordId = idlThing[this.pkeyField]();
         return new Promise((resolve, reject) => {
             this.editDialog.open({size: this.dialogSize}).subscribe(
                 result => {
@@ -284,7 +284,7 @@ export class AdminPageComponent implements OnInit {
         this.editDialog.mode = 'create';
         // We reuse the same editor for all actions.  Be sure
         // create action does not try to modify an existing record.
-        this.editDialog.recId = null;
+        this.editDialog.recordId = null;
         this.editDialog.record = null;
         this.editDialog.open({size: this.dialogSize}).subscribe(
             ok => {