LP1904036 Grid flat data service/support
authorBill Erickson <berickxx@gmail.com>
Fri, 16 Apr 2021 20:37:24 +0000 (16:37 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:31 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-flat-data.service.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
Open-ILS/src/eg2/src/app/share/grid/grid.ts

index 866adec..6f73bd5 100644 (file)
@@ -58,7 +58,7 @@ export class DialogComponent implements OnInit {
     observer: Observer<any>;
 
     // The modalRef allows direct control of the modal instance.
-    private modalRef: NgbModalRef = null;
+    protected modalRef: NgbModalRef = null;
 
     constructor(private modalService: NgbModal) {}
 
index 1bb80fa..be3b2b1 100644 (file)
     </div>
     <div class="row pt-1" *ngFor="let col of columnSet.columns"
       [ngClass]="{visible : col.visible}">
-      <div class="col-lg-1" (click)="col.visible=!col.visible">
+      <div class="col-lg-1" (click)="toggleVisibility(col)">
         <span *ngIf="col.visible" class="badge badge-success">&#x2713;</span>
         <span *ngIf="!col.visible" class="badge badge-warning">&#x2717;</span>
       </div>
-      <div class="col-lg-3" (click)="col.visible=!col.visible">{{col.label}}</div>
+      <div class="col-lg-3" (click)="toggleVisibility(col)">{{col.label}}</div>
       <div class="col-lg-1">
         <a class="no-href" title="Move column up" i18n-title
           (click)="columnSet.moveColumn(col, -1)">
index 10ad606..e4260bb 100644 (file)
@@ -1,6 +1,8 @@
 import {Component, Input, OnInit} from '@angular/core';
+import {Observable} from 'rxjs';
 import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {GridColumnSet} from './grid';
+import {GridColumn, GridColumnSet, GridContext} from './grid';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
 
 @Component({
   selector: 'eg-grid-column-config',
@@ -10,7 +12,34 @@ import {GridColumnSet} from './grid';
 /**
  */
 export class GridColumnConfigComponent extends DialogComponent implements OnInit {
-    @Input() columnSet: GridColumnSet;
+    @Input() gridContext: GridContext;
+    columnSet: GridColumnSet;
+    changesPending = false;
+
+    open(ops: NgbModalOptions): Observable<any> {
+        this.changesPending = false;
+        this.columnSet = this.gridContext.columnSet;
+        return super.open(ops);
+    }
+
+    toggleVisibility(col: GridColumn) {
+        col.visible = !col.visible;
+        this.changesPending = true;
+    }
+
+    // Avoid reloading on each column change and instead reload the
+    // data if needed after all changes are complete.
+    // Override close() so we can reload data if needed.
+    // NOTE: ng-bootstrap v 8.0.0 has a 'closed' emitter, but
+    // we're not there yet.
+    close(value?: any) {
+        if (this.modalRef) { this.modalRef.close(); }
+        this.finalize();
+
+        if (this.changesPending && this.gridContext.reloadOnColumnChange) {
+            this.gridContext.reloadWithoutPagerReset();
+        }
+    }
 }
 
 
index f3651f3..9df9355 100644 (file)
@@ -49,6 +49,10 @@ export class GridColumnComponent implements OnInit {
     @Input() disableTooltip: boolean;
     @Input() asyncSupportsEmptyTermClick: boolean;
 
+    // Required columns are those that must be present in any auto-generated
+    // queries regardless of whether they are visible in the display.
+    @Input() required = false;
+
     // get a reference to our container grid.
     constructor(@Host() private grid: GridComponent) {}
 
@@ -64,6 +68,7 @@ export class GridColumnComponent implements OnInit {
         col.path = this.path;
         col.label = this.label;
         col.flex = this.flex;
+        col.required = this.required;
         col.hidden = this.hidden === true;
         col.asyncSupportsEmptyTermClick = this.asyncSupportsEmptyTermClick === true;
         col.isIndex = this.index === true;
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-flat-data.service.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-flat-data.service.ts
new file mode 100644 (file)
index 0000000..dbb7f6e
--- /dev/null
@@ -0,0 +1,64 @@
+import {Injectable, EventEmitter, TemplateRef} from '@angular/core';
+import {Observable, empty, throwError} from 'rxjs';
+import {tap} from 'rxjs/operators';
+import {StoreService} from '@eg/core/store.service';
+import {LocaleService} from '@eg/core/locale.service';
+import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {GridContext, GridColumnSort} from './grid';
+import {Pager} from '@eg/share/util/pager';
+
+interface FlatQueryFields {
+    [name: string]: string;
+}
+
+
+@Injectable()
+export class GridFlatDataService {
+
+    constructor(
+        private net: NetService,
+        private auth: AuthService
+    ) {}
+
+
+    getRows(gridContext: GridContext,
+        query: any, pager: Pager, sort: GridColumnSort[]): Observable<any> {
+
+        if (!gridContext.idlClass) {
+            return throwError('GridFlatDataService requires an idlClass');
+        }
+
+        const fields = this.compileFields(gridContext);
+        const flatSort = sort.map(s => {
+            const obj: any = {};
+            obj[s.name] = s.dir;
+            return obj;
+        });
+
+        return this.net.request(
+            'open-ils.fielder',
+            'open-ils.fielder.flattened_search',
+            this.auth.token(), gridContext.idlClass,
+            fields, query, {
+                sort: flatSort,
+                limit: pager.limit,
+                offset: pager.offset
+            }
+        );
+    }
+
+    compileFields(gridContext: GridContext): FlatQueryFields {
+        const fields: FlatQueryFields = {};
+
+        gridContext.columnSet.requiredColumns().forEach(col => {
+            // Verify the column describes a proper IDL field
+            const path = col.path || col.name;
+            const info = gridContext.columnSet.idlInfoFromDotpath(path);
+            if (info) { fields[col.name] = path; }
+        });
+
+        return fields;
+    }
+}
+
index db5cd3d..1c2407b 100644 (file)
       class="material-icons mat-icon-in-button">expand_less</span>
   </button>
 
-  <eg-grid-column-config #columnConfDialog [columnSet]="gridContext.columnSet">
+  <eg-grid-column-config #columnConfDialog [gridContext]="gridContext">
   </eg-grid-column-config>
   <div ngbDropdown placement="bottom-right">
     <button ngbDropdownToggle class="btn btn-outline-dark no-dropdown-caret">
       <div class="dropdown-divider"></div>
 
       <a class="dropdown-item label-with-material-icon"
-        (click)="col.visible=!col.visible"
+        (click)="toggleVisibility(col)"
         *ngFor="let col of gridContext.columnSet.sortForColPicker()">
         <span *ngIf="col.visible" class="badge badge-success">&#x2713;</span>
         <span *ngIf="!col.visible" class="badge badge-warning">&#x2717;</span>
index 7f68063..e4b2ed4 100644 (file)
@@ -4,6 +4,7 @@ import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
 import {GridToolbarButton, GridToolbarAction, GridContext} from '@eg/share/grid/grid';
 import {GridColumnWidthComponent} from './grid-column-width.component';
 import {GridPrintComponent} from './grid-print.component';
+import {GridColumn} from './grid';
 
 @Component({
   selector: 'eg-grid-toolbar',
@@ -129,6 +130,13 @@ export class GridToolbarComponent implements OnInit {
 
         $event.preventDefault();
     }
+
+    toggleVisibility(col: GridColumn) {
+        col.visible = !col.visible;
+        if (this.gridContext.reloadOnColumnChange) {
+            this.gridContext.reloadWithoutPagerReset();
+        }
+    }
 }
 
 
index 1bb94e9..eab8442 100644 (file)
@@ -123,6 +123,10 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
     // If set, appears along the top left side of the grid.
     @Input() toolbarLabel: string;
 
+    // If true, showing/hiding columns will force the data source to
+    // refresh the current page of data.
+    @Input() reloadOnColumnChange = false;
+
     context: GridContext;
 
     // These events are emitted from our grid-body component.
@@ -172,6 +176,7 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
         this.context.disablePaging = this.disablePaging === true;
         this.context.cellTextGenerator = this.cellTextGenerator;
         this.context.ignoredFields = [];
+        this.context.reloadOnColumnChange = this.reloadOnColumnChange;
 
         if (this.showFields) {
             // Stripping spaces allows users to add newlines to
index 0757fab..579235e 100644 (file)
@@ -16,6 +16,7 @@ import {GridColumnWidthComponent} from './grid-column-width.component';
 import {GridPrintComponent} from './grid-print.component';
 import {GridFilterControlComponent} from './grid-filter-control.component';
 import {GridToolbarActionsEditorComponent} from './grid-toolbar-actions-editor.component';
+import {GridFlatDataService} from './grid-flat-data.service';
 
 
 @NgModule({
@@ -50,6 +51,7 @@ import {GridToolbarActionsEditorComponent} from './grid-toolbar-actions-editor.c
         GridToolbarActionComponent
     ],
     providers: [
+      GridFlatDataService
     ]
 })
 
index 085b18e..6a4bd01 100644 (file)
@@ -2,7 +2,7 @@
  * Collection of grid related classses and interfaces.
  */
 import {TemplateRef, EventEmitter, QueryList} from '@angular/core';
-import {Observable, Subscription} from 'rxjs';
+import {Observable, Subscription, empty} from 'rxjs';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {ServerStoreService} from '@eg/core/server-store.service';
@@ -42,6 +42,7 @@ export class GridColumn {
     disableTooltip: boolean;
     asyncSupportsEmptyTermClick: boolean;
     comparator: (valueA: any, valueB: any) => number;
+    required = false;
 
     // True if the column was automatically generated.
     isAuto: boolean;
@@ -288,6 +289,12 @@ export class GridColumnSet {
         return visible.concat(invisible);
     }
 
+    requiredColumns(): GridColumn[] {
+        const visible = this.displayColumns();
+        return visible.concat(
+            this.columns.filter(c => c.required && !c.visible));
+    }
+
     insertBefore(source: GridColumn, target: GridColumn) {
         let targetIdx = -1;
         let sourceIdx = -1;
@@ -550,6 +557,7 @@ export class GridContext {
     disablePaging: boolean;
     showDeclaredFieldsOnly: boolean;
     cellTextGenerator: GridCellTextGenerator;
+    reloadOnColumnChange: boolean;
 
     // Allow calling code to know when the select-all-rows-in-page
     // action has occurred.
@@ -1268,15 +1276,20 @@ export class GridToolbarCheckbox {
     onChange: EventEmitter<boolean>;
 }
 
+export interface GridColumnSort {
+    name: string;
+    dir: string;
+}
+
 export class GridDataSource {
 
     data: any[];
-    sort: any[];
+    sort: GridColumnSort[];
     filters: Object;
     allRowsRetrieved: boolean;
     requestingData: boolean;
     retrievalError: boolean;
-    getRows: (pager: Pager, sort: any[]) => Observable<any>;
+    getRows: (pager: Pager, sort: GridColumnSort[]) => Observable<any>;
 
     constructor() {
         this.sort = [];
@@ -1356,4 +1369,3 @@ export class GridDataSource {
     }
 }
 
-