LP#1775466 Nested grid fields support
authorBill Erickson <berickxx@gmail.com>
Fri, 15 Jun 2018 15:51:58 +0000 (11:51 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 15 Jun 2018 15:51:58 +0000 (11:51 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
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/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html

index 5f93682..51565dc 100644 (file)
@@ -20,8 +20,8 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
     @Input() mainLabel: string;
     @Input() dataSource: GridDataSource;
     @Input() idlClass: string;
-    @Input() isSortable: boolean;
-    @Input() isMultiSortable: boolean;
+    @Input() sortable: boolean;
+    @Input() multiSortable: boolean;
     @Input() persistKey: string;
     @Input() disableMultiSelect: boolean;
 
@@ -46,8 +46,8 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
         this.context.idlClass = this.idlClass;
         this.context.dataSource = this.dataSource;
         this.context.persistKey = this.persistKey;
-        this.context.isSortable = this.isSortable === true;
-        this.context.isMultiSortable = this.isMultiSortable === true;
+        this.context.isSortable = this.sortable === true;
+        this.context.isMultiSortable = this.multiSortable === true;
         this.context.disableMultiSelect = this.disableMultiSelect === true;
         this.context.init();
     }
index 480ffe0..876c2e9 100644 (file)
@@ -30,6 +30,7 @@ export class GridColumn {
     isDragTarget: boolean;
     isSortable: boolean;
     isMultiSortable: boolean;
+    flesher: (obj: any, col: GridColumn, item: any) => any;
 
     getCellContext(row: any) {
         return {
@@ -47,24 +48,22 @@ export class GridColumnSet {
     isSortable: boolean;
     isMultiSortable: boolean;
     stockVisible: string[];
+    idl: IdlService;
 
-    constructor(idlClass?: string) {
+    constructor(idl: IdlService, idlClass?: string) {
+        this.idl = idl;
         this.columns = [];
         this.stockVisible = [];
         this.idlClass = idlClass;
     }
 
     add(col: GridColumn) {
-        // avoid dupes
-        if (this.getColByName(col.name)) { return; }
 
-        if (col.isIndex) { this.indexColumn = col; }
-        if (!col.flex) { col.flex = 2; }
-        if (!col.label) { col.label = col.name; }
-        if (!col.align) { col.align = 'left'; }
-        if (!col.datatype) { col.datatype = 'text'; }
+        this.applyColumnDefaults(col);
 
-        col.visible = !col.hidden;
+        if (this.getColByName(col.name)) { return; } // avoid dupes
+
+        if (col.isIndex) { this.indexColumn = col; }
 
         // track which fields are visible on page load.
         if (col.visible) {
@@ -80,6 +79,39 @@ export class GridColumnSet {
         return this.columns.filter(c => c.name === name)[0];
     }
 
+    idlInfoFromDotpath(dotpath: string): any {
+        if (!dotpath) return null;
+
+        let idlParent;
+        let idlField;
+        let idlClass = this.idl.classes[this.idlClass];
+
+        const pathParts = dotpath.split(/\./);
+
+        for (let i = 0; i < pathParts.length; i++) {
+            const part = pathParts[i];
+            idlParent = idlField;
+            idlField = idlClass.field_map[part];
+
+            if (idlField) {
+                if (idlField['class'] && (
+                    idlField.datatype === 'link' || 
+                    idlField.datatype === 'org_unit')) {
+                    idlClass = this.idl.classes[idlField['class']];
+                }
+            } else {
+                return null;
+            }
+        }
+
+        return {
+            idlParent: idlParent,
+            idlField : idlField,
+            idlClass : idlClass
+        };
+    }
+
+
     reset() {
         this.columns.forEach(col => {
             col.flex = 2;
@@ -89,6 +121,28 @@ export class GridColumnSet {
         });
     }
 
+    applyColumnDefaults(col: GridColumn) {
+
+        if (!col.idlFieldDef && col.path) {
+            const idlInfo = this.idlInfoFromDotpath(col.path);
+            if (idlInfo) {
+                col.idlFieldDef = idlInfo.idlField;
+                if (!col.label) {
+                    col.label = col.idlFieldDef.label || col.idlFieldDef.name;
+                    col.datatype = col.idlFieldDef.datatype;
+                }
+            }
+        }
+
+        if (!col.name) { col.name = col.path; }
+        if (!col.flex) { col.flex = 2; }
+        if (!col.align) { col.align = 'left'; }
+        if (!col.label) { col.label = col.name; }
+        if (!col.datatype) { col.datatype = 'text'; }
+
+        col.visible = !col.hidden;
+    }
+
     applyColumnSortability(col: GridColumn) {
         // column sortability defaults to the sortability of the column set.
         if (col.isSortable === undefined && this.isSortable) {
@@ -297,7 +351,7 @@ export class GridContext {
     }
 
     init() {
-        this.columnSet = new GridColumnSet(this.idlClass);
+        this.columnSet = new GridColumnSet(this.idl, this.idlClass);
         this.columnSet.isSortable = this.isSortable === true;
         this.columnSet.isMultiSortable = this.isMultiSortable === true;
         this.generateColumns();
@@ -313,8 +367,8 @@ export class GridContext {
 
     destroy() {
         this.ignorePager();
-
     }
+
     reload() {
         // Give the UI time to settle before reloading grid data.
         // This can help when data retrieval depends on a value
@@ -381,14 +435,68 @@ export class GridContext {
 
     getRowColumnValue(row: any, col: GridColumn): string {
         let val;
-        if (typeof row[col.name] === 'function') {
-            val = row[col.name]();
+        if (col.name in row) {
+            val = this.getObjectFieldValue(row, col.name);
         } else {
-            val = row[col.name];
+            if (col.path) {
+                val = this.nestedItemFieldValue(row, col);
+            }
         }
         return this.format.transform({value: val, datatype: col.datatype});
     }
 
+    getObjectFieldValue(obj: any, name: string): any {
+        if (typeof obj[name] === 'function') {
+            return obj[name]();
+        } else {
+            return obj[name];
+        }
+    }
+
+    nestedItemFieldValue(obj: any, col: GridColumn): string {
+
+        let idlField;
+        let idlClassDef;
+        const original = obj;
+        const steps = col.path.split('.');
+
+        for (let i = 0; i < steps.length; i++) {
+            const step = steps[i];
+
+            if (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.
+                if (col.flesher && obj !== undefined) {
+                    return col.flesher(obj, col, original);
+                }
+                return obj;
+            }
+
+            const class_ = obj.classname;
+            if (class_ && (idlClassDef = this.idl.classes[class_])) {
+                idlField = idlClassDef.field_map[step];
+            }
+
+            obj = this.getObjectFieldValue(obj, step);
+        }
+
+        // We found a nested IDL object which may or may not have 
+        // been configured as a top-level column.  Flesh the column
+        // metadata with our newly found IDL info.
+        if (idlField) {
+            if (!col.datatype) {
+                col.datatype = idlField.datatype;
+            }
+            if (!col.label) {
+                col.label = idlField.label || idlField.name;
+            }
+        }
+
+        return obj;
+    }
+
+
     getColumnTextContent(row: any, col: GridColumn): string {
         if (col.cellTemplate) {
             // TODO
index fda77a4..b96a7ce 100644 (file)
   HELLO {{userContext.hello}}
   <button>{{row.id()}}</button>
 </ng-template>
-<eg-grid #cbtGrid idlClass="cbt" [dataSource]="btSource">
-  <eg-grid-column name="test" [cellTemplate]="cellTmpl" [cellContext]="btGridTestContext">
+<eg-grid #cbtGrid idlClass="cbt" [dataSource]="btSource" [sortable]="true">
+  <eg-grid-column name="test" [cellTemplate]="cellTmpl" 
+    [cellContext]="btGridTestContext" [sortable]="false">
   </eg-grid-column>
+  <eg-grid-column [sortable]="false" path="owner.name"></eg-grid-column>
 </eg-grid>
 
+<br/><br/>
+
 
index 1dc600f..46f2bd5 100644 (file)
@@ -9,6 +9,7 @@ import {take} from 'rxjs/operators/take';
 import {GridDataSource} from '@eg/share/grid/grid';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
 import {Pager} from '@eg/share/util/pager';
 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
 import {PrintService} from '@eg/share/print/print.service';
@@ -52,6 +53,7 @@ export class SandboxComponent implements OnInit {
 
     constructor(
         private idl: IdlService,
+        private org: OrgService,
         private pcrud: PcrudService,
         private strings: StringService,
         private toast: ToastService,
@@ -66,12 +68,22 @@ export class SandboxComponent implements OnInit {
             {name: 'The Tick', state: 'TX'}
         ];
 
-        this.btSource.getRows = (pager: Pager) => {
+        this.btSource.getRows = (pager: Pager, sort: any[]) => {
+
+            const orderBy: any = {cbt: 'name'};
+            if (sort.length) {
+                orderBy.cbt = sort[0].name + ' ' + sort[0].dir;
+            }
+            
             return this.pcrud.retrieveAll('cbt', {
                 offset: pager.offset,
                 limit: pager.limit,
-                order_by: {cbt: 'name'}
-            });
+                order_by: orderBy
+            }).pipe(map(cbt => {
+                // example of inline fleshing
+                cbt.owner(this.org.get(cbt.owner()));
+                return cbt;
+            }));
         };
 
         /*
index 9c9c7ed..600db00 100644 (file)
@@ -38,7 +38,7 @@
 </ng-container>
 
 <eg-grid #grid idlClass="{{idlClass}}" [dataSource]="dataSource" 
-    [isSortable]="true" persistKey="{{persistKey}}">
+    [sortable]="true" persistKey="{{persistKey}}">
   <eg-grid-toolbar-button label="New {{idlClassDef.label}}" i18n-label [action]="createNew">
   </eg-grid-toolbar-button>
   <eg-grid-toolbar-action label="Delete Selected" i18n-label [action]="deleteSelected">