From 8480ca2446a3c73e505a59f755a19cc4dd30731b Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 25 Jan 2019 15:17:56 -0500 Subject: [PATCH] LP1812670 Angular grid shows selector labels * 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 Signed-off-by: Dan Wells --- Open-ILS/src/eg2/src/app/core/idl.service.ts | 14 +++++++ .../src/eg2/src/app/core/pcrud.service.ts | 40 +++++++++++++++++++ .../share/fm-editor/fm-editor.component.ts | 3 +- .../eg2/src/app/share/grid/grid.component.ts | 15 +++++++ Open-ILS/src/eg2/src/app/share/grid/grid.ts | 21 +++++++--- .../admin-page/admin-page.component.html | 2 +- .../share/admin-page/admin-page.component.ts | 6 ++- 7 files changed, 90 insertions(+), 11 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/core/idl.service.ts b/Open-ILS/src/eg2/src/app/core/idl.service.ts index 89f8411de9..b6f8173159 100644 --- a/Open-ILS/src/eg2/src/app/core/idl.service.ts +++ b/Open-ILS/src/eg2/src/app/core/idl.service.ts @@ -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; + } } diff --git a/Open-ILS/src/eg2/src/app/core/pcrud.service.ts b/Open-ILS/src/eg2/src/app/core/pcrud.service.ts index fc79f30d09..f023b6d2f5 100644 --- a/Open-ILS/src/eg2/src/app/core/pcrud.service.ts +++ b/Open-ILS/src/eg2/src/app/core/pcrud.service.ts @@ -13,6 +13,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. @@ -86,10 +90,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 { 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]); @@ -112,6 +148,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]); } diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts index 17c0e46dd1..45dd167bb6 100644 --- a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts @@ -273,9 +273,8 @@ export class FmRecordEditorComponent // linked data so we can display the data within the selector // field. Otherwise, avoid the network lookup and let the // bare value (usually an ID) be displayed. - const idField = this.idl.classes[field.class].pkey; const selector = - this.idl.classes[field.class].field_map[idField].selector; + this.idl.getLinkSelector(this.idlClass, field.name); if (selector && selector !== field.name) { promises.push( diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts index d48028b938..66686ef2e8 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts @@ -80,6 +80,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. @@ -112,6 +126,7 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy { this.context.isMultiSortable = this.multiSortable === true; this.context.useLocalSort = this.useLocalSort === true; this.context.disableSelect = this.disableSelect === true; + this.context.showLinkSelectors = this.showLinkSelectors === true; this.context.disableMultiSelect = this.disableMultiSelect === true; this.context.rowFlairIsEnabled = this.rowFlairIsEnabled === true; this.context.rowFlairCallback = this.rowFlairCallback; diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts index dcffc95143..16c5ea1279 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts @@ -437,6 +437,7 @@ export class GridContext { defaultVisibleFields: string[]; defaultHiddenFields: string[]; overflowCells: boolean; + showLinkSelectors: boolean; // Services injected by our grid component idl: IdlService; @@ -624,12 +625,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({ @@ -657,7 +657,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. @@ -861,6 +861,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); }); } diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html index 44e407b862..95819f0a0a 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html @@ -49,7 +49,7 @@ + [sortable]="true" persistKey="{{persistKey}}" [showLinkSelectors]="true"> 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 cd6e706660..4561a04c23 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 @@ -302,11 +302,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}); }; } -- 2.43.2