LP1803787 Grid context retains selection; lint
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / share / grid / grid.ts
index 7c30438..cfed24e 100644 (file)
@@ -26,12 +26,14 @@ export class GridColumn {
     idlFieldDef: any;
     datatype: string;
     datePlusTime: boolean;
+    ternaryBool: boolean;
     cellTemplate: TemplateRef<any>;
     cellContext: any;
     isIndex: boolean;
     isDragTarget: boolean;
     isSortable: boolean;
     isMultiSortable: boolean;
+    disableTooltip: boolean;
     comparator: (valueA: any, valueB: any) => number;
 
     // True if the column was automatically generated.
@@ -128,7 +130,7 @@ export class GridColumnSet {
                 if (idx === 0) {
                     this.columns.unshift(col);
                 } else {
-                    this.columns.splice(idx - 1, 0, col);
+                    this.columns.splice(idx, 0, col);
                 }
                 return true;
             }
@@ -441,6 +443,12 @@ export class GridContext {
     defaultHiddenFields: string[];
     overflowCells: boolean;
     showLinkSelectors: boolean;
+    disablePaging: boolean;
+    showDeclaredFieldsOnly: boolean;
+
+    // Allow calling code to know when the select-all-rows-in-page
+    // action has occurred.
+    selectRowsInPageEmitter: EventEmitter<void>;
 
     // Services injected by our grid component
     idl: IdlService;
@@ -459,7 +467,6 @@ export class GridContext {
         this.store = store;
         this.format = format;
         this.pager = new Pager();
-        this.pager.limit = 10;
         this.rowSelector = new GridRowSelector();
         this.toolbarButtons = [];
         this.toolbarCheckboxes = [];
@@ -467,11 +474,15 @@ export class GridContext {
     }
 
     init() {
+        this.selectRowsInPageEmitter = new EventEmitter<void>();
         this.columnSet = new GridColumnSet(this.idl, this.idlClass);
         this.columnSet.isSortable = this.isSortable === true;
         this.columnSet.isMultiSortable = this.isMultiSortable === true;
         this.columnSet.defaultHiddenFields = this.defaultHiddenFields;
         this.columnSet.defaultVisibleFields = this.defaultVisibleFields;
+        if (!this.pager.limit) {
+            this.pager.limit = this.disablePaging ? MAX_ALL_ROW_COUNT : 10;
+        }
         this.generateColumns();
     }
 
@@ -640,6 +651,13 @@ export class GridContext {
         return selected;
     }
 
+    rowIsSelected(row: any): boolean {
+        const index = this.getRowIndex(row);
+        return this.rowSelector.selected().filter(
+            idx => idx === index
+        ).length > 0;
+    }
+
     getRowColumnValue(row: any, col: GridColumn): string {
         let val;
 
@@ -649,6 +667,12 @@ export class GridContext {
             val = this.getObjectFieldValue(row, col.name);
         }
 
+        if (col.datatype === 'bool') {
+            // Avoid string-ifying bools so we can use an <eg-bool/>
+            // in the grid template.
+            return val;
+        }
+
         return this.format.transform({
             value: val,
             idlClass: col.idlClass,
@@ -731,6 +755,12 @@ export class GridContext {
         this.lastSelectedIndex = index;
     }
 
+    selectMultipleRows(indexes: any[]) {
+        this.rowSelector.clear();
+        this.rowSelector.select(indexes);
+        this.lastSelectedIndex = indexes[indexes.length - 1];
+    }
+
     // selects or deselects an item, without affecting the others.
     // returns true if the item is selected; false if de-selected.
     toggleSelectOneRow(index: any) {
@@ -770,12 +800,84 @@ export class GridContext {
         }
     }
 
+    // shift-up-arrow
+    // Select the previous row in addition to any currently selected row.
+    // However, if the previous row is already selected, assume the user
+    // has reversed direction and now wants to de-select the last selected row.
+    selectMultiRowsPrevious() {
+        if (!this.lastSelectedIndex) { return; }
+        const pos = this.getRowPosition(this.lastSelectedIndex);
+        const selectedIndexes = this.rowSelector.selected();
+
+        const promise = // load the previous page of data if needed
+            (pos === this.pager.offset) ? this.toPrevPage() : Promise.resolve();
+
+        promise.then(
+            ok => {
+                const row = this.dataSource.data[pos - 1];
+                const newIndex = this.getRowIndex(row);
+                if (selectedIndexes.filter(i => i === newIndex).length > 0) {
+                    // Prev row is already selected.  User is reversing direction.
+                    this.rowSelector.deselect(this.lastSelectedIndex);
+                    this.lastSelectedIndex = newIndex;
+                } else {
+                    this.selectMultipleRows(selectedIndexes.concat(newIndex));
+                }
+            },
+            err => {}
+        );
+    }
+
+    // shift-down-arrow
+    // Select the next row in addition to any currently selected row.
+    // However, if the next row is already selected, assume the user
+    // has reversed direction and wants to de-select the last selected row.
+    selectMultiRowsNext() {
+        if (!this.lastSelectedIndex) { return; }
+        const pos = this.getRowPosition(this.lastSelectedIndex);
+        const selectedIndexes = this.rowSelector.selected();
+
+        const promise = // load the next page of data if needed
+            (pos === (this.pager.offset + this.pager.limit - 1)) ?
+            this.toNextPage() : Promise.resolve();
+
+        promise.then(
+            ok => {
+                const row = this.dataSource.data[pos + 1];
+                const newIndex = this.getRowIndex(row);
+                if (selectedIndexes.filter(i => i === newIndex).length > 0) {
+                    // Next row is already selected.  User is reversing direction.
+                    this.rowSelector.deselect(this.lastSelectedIndex);
+                    this.lastSelectedIndex = newIndex;
+                } else {
+                    this.selectMultipleRows(selectedIndexes.concat(newIndex));
+                }
+            },
+            err => {}
+        );
+    }
+
+    getFirstRowInPage(): any {
+        return this.dataSource.data[this.pager.offset];
+    }
+
+    getLastRowInPage(): any {
+        return this.dataSource.data[this.pager.offset + this.pager.limit - 1];
+    }
+
     selectFirstRow() {
-        this.selectRowByPos(this.pager.offset);
+        this.selectOneRow(this.getRowIndex(this.getFirstRowInPage()));
     }
 
     selectLastRow() {
-        this.selectRowByPos(this.pager.offset + this.pager.limit - 1);
+        this.selectOneRow(this.getRowIndex(this.getLastRowInPage()));
+    }
+
+    selectRowsInPage() {
+        const rows = this.dataSource.getPageOfRows(this.pager);
+        const indexes = rows.map(r => this.getRowIndex(r));
+        this.rowSelector.select(indexes);
+        this.selectRowsInPageEmitter.emit();
     }
 
     toPrevPage(): Promise<any> {
@@ -896,6 +998,10 @@ export class GridContext {
                 }
             }
 
+            if (this.showDeclaredFieldsOnly) {
+                col.hidden = true;
+            }
+
             this.columnSet.add(col);
         });
     }
@@ -927,7 +1033,9 @@ export class GridToolbarAction {
     onClick: EventEmitter<any []>;
     action: (rows: any[]) => any; // DEPRECATED
     group: string;
+    disabled: boolean;
     isGroup: boolean; // used for group placeholder entries
+    separator: boolean;
     disableOnRows: (rows: any[]) => boolean;
 }
 
@@ -950,6 +1058,7 @@ export class GridDataSource {
     data: any[];
     sort: any[];
     allRowsRetrieved: boolean;
+    requestingData: boolean;
     getRows: (pager: Pager, sort: any[]) => Observable<any>;
 
     constructor() {
@@ -985,16 +1094,23 @@ export class GridDataSource {
             return Promise.resolve();
         }
 
+        // If we have to call out for data, set inFetch
+        this.requestingData = true;
+
         return new Promise((resolve, reject) => {
             let idx = pager.offset;
             return this.getRows(pager, this.sort).subscribe(
-                row => this.data[idx++] = row,
+                row => {
+                    this.data[idx++] = row;
+                    this.requestingData = false;
+                },
                 err => {
                     console.error(`grid getRows() error ${err}`);
                     reject(err);
                 },
                 ()  => {
                     this.checkAllRetrieved(pager, idx);
+                    this.requestingData = false;
                     resolve();
                 }
             );