LP1812670 Angular grid shows selector labels user/berick/lp1812670-ang-grid-selector-fields-3.2
authorBill Erickson <berickxx@gmail.com>
Fri, 25 Jan 2019 20:17:56 +0000 (15:17 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 25 Jan 2019 20:44:54 +0000 (15:44 -0500)
* Teach PcrudService how to flesh link fields when a selector is defined
  on the linked class. This uses a new search/retrieve API flag
  {fleshSelectors:true}.

* Teach the grid how to render selector values when configured to do so
  via a new grid component attribute [showLinkSelectors]="true".

* Teach the Angular staff admin page to request linked selectors from
  pcrud and tell its grid to expect them.

* Adds utility function to IdlServer for finding the selector for a
  given class + field.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/core/idl.service.ts
Open-ILS/src/eg2/src/app/core/pcrud.service.ts
Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.ts
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts

index 89f8411..b6f8173 100644 (file)
@@ -133,5 +133,19 @@ export class IdlService {
 
         return result;
     }
+
+    // Given a field on an IDL class, returns the name of the field
+    // on the linked class that acts as the selector for the linked class.
+    // Returns null if no selector is found or the field is not a link.
+    getLinkSelector(fmClass: string, field: string): string {
+        const fieldDef = this.classes[fmClass].field_map[field];
+        if (fieldDef.class) {
+            const classDef = this.classes[fieldDef.class];
+            if (classDef.pkey) {
+                return classDef.field_map[classDef.pkey].selector || null;
+            }
+        }
+        return null;
+    }
 }
 
index 76ee341..3c26fab 100644 (file)
@@ -14,6 +14,10 @@ interface PcrudReqOps {
     anonymous?: boolean;
     idlist?: boolean;
     atomic?: boolean;
+    // If true, link-type fields which link to a class that defines a
+    // selector will be fleshed with the linked value.  This affects
+    // retrieve(), retrieveAll(), and search() calls.
+    fleshSelectors?: boolean;
 }
 
 // For for documentation purposes.
@@ -87,10 +91,42 @@ export class PcrudContext {
         this.session.disconnect();
     }
 
+    // Adds "flesh" logic to retrieve linked values for all fields
+    // that link to a class which defines a selector field.
+    applySelectorFleshing(fmClass: string, pcrudOps: any) {
+        pcrudOps = pcrudOps || {};
+
+        if (!pcrudOps.flesh) {
+            pcrudOps.flesh = 1;
+        }
+
+        if (!pcrudOps.flesh_fields) {
+            pcrudOps.flesh_fields = {};
+        }
+
+        this.idl.classes[fmClass].fields
+        .filter(f => f.datatype === 'link' && !f.virtual)
+        .forEach(field => {
+            const selector = this.idl.getLinkSelector(fmClass, field.name);
+            if (!selector) { return; }
+
+            if (!pcrudOps.flesh_fields[fmClass]) {
+                pcrudOps.flesh_fields[fmClass] = [];
+            }
+
+            if (pcrudOps.flesh_fields[fmClass].indexOf(field.name) < 0) {
+                pcrudOps.flesh_fields[fmClass].push(field.name);
+            }
+        });
+    }
+
     retrieve(fmClass: string, pkey: Number | string,
             pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
         reqOps = reqOps || {};
         this.authoritative = reqOps.authoritative || false;
+        if (reqOps.fleshSelectors) {
+            this.applySelectorFleshing(fmClass, pcrudOps);
+        }
         return this.dispatch(
             `open-ils.pcrud.retrieve.${fmClass}`,
              [this.token(reqOps), pkey, pcrudOps]);
@@ -113,6 +149,10 @@ export class PcrudContext {
 
         if (reqOps.atomic) { method += '.atomic'; }
 
+        if (reqOps.fleshSelectors) {
+            this.applySelectorFleshing(fmClass, pcrudOps);
+        }
+
         return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
     }
 
index 1fa4c2c..b9771e0 100644 (file)
@@ -78,6 +78,20 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
     // grid data.
     @Input() pageOffset: number;
 
+    // If true and an idlClass is specificed, the grid assumes
+    // datatype=link fields that link to classes which define a selector
+    // are fleshed with the linked object.  And, instead of displaying
+    // the raw field value, displays the selector value from the linked
+    // object.  The caller is responsible for fleshing the appropriate
+    // fields in the GridDataSource getRows handler.
+    //
+    // This only applies to auto-generated columns.
+    //
+    // For example, idlClass="aou" and field="ou_type", the display
+    // value will be ou_type().name() since "name" is the selector
+    // field on the "aout" class.
+    @Input() showLinkSelectors: boolean;
+
     context: GridContext;
 
     // These events are emitted from our grid-body component.
@@ -109,6 +123,7 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
         this.context.isSortable = this.sortable === true;
         this.context.isMultiSortable = this.multiSortable === true;
         this.context.useLocalSort = this.useLocalSort === true;
+        this.context.showLinkSelectors = this.showLinkSelectors === true;
         this.context.disableMultiSelect = this.disableMultiSelect === true;
         this.context.rowFlairIsEnabled = this.rowFlairIsEnabled  === true;
         this.context.rowFlairCallback = this.rowFlairCallback;
index 8a10703..b4ad72d 100644 (file)
@@ -436,6 +436,7 @@ export class GridContext {
     defaultVisibleFields: string[];
     defaultHiddenFields: string[];
     overflowCells: boolean;
+    showLinkSelectors: boolean;
 
     // Services injected by our grid component
     idl: IdlService;
@@ -623,12 +624,11 @@ export class GridContext {
 
     getRowColumnValue(row: any, col: GridColumn): string {
         let val;
-        if (col.name in row) {
+
+        if (col.path) {
+            val = this.nestedItemFieldValue(row, col);
+        } else if (col.name in row) {
             val = this.getObjectFieldValue(row, col.name);
-        } else {
-            if (col.path) {
-                val = this.nestedItemFieldValue(row, col);
-            }
         }
         return this.format.transform({value: val, datatype: col.datatype});
     }
@@ -651,7 +651,7 @@ export class GridContext {
         for (let i = 0; i < steps.length; i++) {
             const step = steps[i];
 
-            if (typeof obj !== 'object') {
+            if (obj === null || obj === undefined || typeof obj !== 'object') {
                 // We have run out of data to step through before
                 // reaching the end of the path.  Conclude fleshing via
                 // callback if provided then exit.
@@ -855,6 +855,15 @@ export class GridContext {
             col.datatype = field.datatype;
             col.isIndex = (field.name === pkeyField);
             col.isAuto = true;
+
+            if (this.showLinkSelectors) {
+                const selector = this.idl.getLinkSelector(
+                    this.columnSet.idlClass, field.name);
+                if (selector) {
+                    col.path = field.name + '.' + selector;
+                }
+            }
+
             this.columnSet.add(col);
         });
     }
index 194f06b..6878926 100644 (file)
@@ -42,7 +42,7 @@
 <eg-translate #translator></eg-translate>
 
 <eg-grid #grid idlClass="{{idlClass}}" [dataSource]="dataSource" 
-    [sortable]="true" persistKey="{{persistKey}}">
+    [sortable]="true" persistKey="{{persistKey}}" [showLinkSelectors]="true">
   <eg-grid-toolbar-button [disabled]="!canCreate" 
     label="New {{idlClassDef.label}}" i18n-label [action]="createNew">
   </eg-grid-toolbar-button>
index be4452b..f92174a 100644 (file)
@@ -289,11 +289,13 @@ export class AdminPageComponent implements OnInit {
 
                 const search = {};
                 search[this.orgField] = orgs;
-                return this.pcrud.search(this.idlClass, search, searchOps);
+                return this.pcrud.search(
+                    this.idlClass, search, searchOps, {fleshSelectors: true});
             }
 
             // No org filter -- fetch all rows
-            return this.pcrud.retrieveAll(this.idlClass, searchOps);
+            return this.pcrud.retrieveAll(
+                this.idlClass, searchOps, {fleshSelectors: true});
         };
     }