LP#1626157 Grid / billing types continued
authorBill Erickson <berickxx@gmail.com>
Fri, 11 May 2018 20:04:21 +0000 (16:04 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 11 May 2018 20:04:21 +0000 (16:04 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.component.css
Open-ILS/src/eg2/src/app/share/grid/grid.component.html
Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.service.ts
Open-ILS/src/eg2/src/app/share/util/pager.ts
Open-ILS/src/eg2/src/app/staff/admin/server/config/billing_type.component.ts

index b992adc..18623f0 100644 (file)
@@ -1,7 +1,7 @@
 
 <div class="eg-grid-row eg-grid-header-row">
   <div class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
-    <input type='checkbox'> <!-- add click handlers ; shared selector mod -->
+    <input type='checkbox' (click)="handleBatchSelect($event)">
   </div>
   <div class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
     <span i18n="number|Row Number Header">#</span>
index 2b9f00d..fc08727 100644 (file)
@@ -1,7 +1,9 @@
 import {Component, Input, OnInit, Host} from '@angular/core';
-import {EgGridService, EgGridColumn, EgGridColumnSet} from './grid.service';
+import {EgGridService, EgGridColumn, 
+    EgGridRowSelector, EgGridColumnSet} from './grid.service';
 import {EgGridDataSource} from './grid-data-source';
 import {EgGridComponent} from './grid.component';
+import {Pager} from '@eg/share/util/pager';
 
 @Component({
   selector: 'eg-grid-header',
@@ -11,8 +13,9 @@ import {EgGridComponent} from './grid.component';
 export class EgGridHeaderComponent implements OnInit {
 
     @Input() columnSet: EgGridColumnSet;
-    @Input() selected: {[idx:number] : boolean};
+    @Input() rowSelector: EgGridRowSelector;
     @Input() dataSource: EgGridDataSource;
+    @Input() pager: Pager;
     dragColumn: EgGridColumn;
 
     constructor(
@@ -57,5 +60,29 @@ export class EgGridHeaderComponent implements OnInit {
         let sort = this.dataSource.sort.filter(c => c.name == col.name)[0];
         return sort && sort.dir == dir;
     }
+
+    handleBatchSelect($event) {
+        if ($event.target.checked) {
+            if (this.rowSelector.isEmpty() || !this.allRowsAreSelected()) {
+                // clear selections from other pages to avoid confusion.
+                this.rowSelector.clear();
+                this.selectAll();
+            }
+        } else {
+            this.rowSelector.clear();
+        }
+    }
+
+    selectAll() {
+        let rows = this.dataSource.getPageOfRows(this.pager);
+        let indexes = rows.map(r => this.grid.getRowIndex(r));
+        this.rowSelector.select(indexes);
+    }
+
+    allRowsAreSelected(): boolean {
+        let rows = this.dataSource.getPageOfRows(this.pager);
+        let indexes = rows.map(r => this.grid.getRowIndex(r));
+        return this.rowSelector.contains(indexes);
+    }
 }
 
index a7f1913..8cc95b9 100644 (file)
@@ -27,10 +27,6 @@ export class EgGridToolbarComponent implements OnInit {
     }
 
     ngOnInit() {
-
-        // listen for pagination changes
-        this.pager.onChange$.subscribe(
-            val => this.dataSource.requestPage(this.pager));
     }
 
     saveColumns() {
index 81fb257..c571b89 100644 (file)
@@ -17,7 +17,8 @@
 .eg-grid-body-row {
 }
 
-.eg-grid-body-row.selected {
+.eg-grid-body-row.selected, 
+.eg-grid-column-config-dialog .visible {
   color: #004085;
   background-color: #cce5ff;
   border-color: #b8daff;
   box-shadow: none;
 }
 
-.eg-grid-column-config-dialog .visible {
-  color: #000;
-  background-color: rgb(201, 221, 225);
-  border-bottom: 1px solid #888;
-}
-
-
-
index 8d5e853..52a58d4 100644 (file)
@@ -6,15 +6,17 @@
     [toolbarButtons]="toolbarButtons" [toolbarActions]="toolbarActions"
     [colWidthConfig]="colWidthConfig" persistKey="{{persistKey}}">
   </eg-grid-toolbar>
-  <eg-grid-header [columnSet]="columnSet" [dataSource]="dataSource"></eg-grid-header>
+  <eg-grid-header [columnSet]="columnSet" [pager]="pager"
+    [dataSource]="dataSource" [rowSelector]="rowSelector">
+  </eg-grid-header>
   <eg-grid-column-width #colWidthConfig [columnSet]="columnSet"></eg-grid-column-width>
 
   <div class="eg-grid-row eg-grid-body-row"
-    [ngClass]="{'selected': selector[getRowIndex(row)]}"
+    [ngClass]="{'selected': rowSelector.contains(getRowIndex(row))}"
     *ngFor="let row of dataSource.getPageOfRows(pager); let idx = index">
 
     <div class="eg-grid-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
-      <input type='checkbox' [(ngModel)]="selector[getRowIndex(row)]">
+      <input type='checkbox' [(ngModel)]="rowSelector.indexes[getRowIndex(row)]">
     </div>
     <div class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
       {{pager.rowNumber(idx)}}
index 2ca4b43..aa4ce60 100644 (file)
@@ -1,11 +1,12 @@
-import {Component, Input, OnInit, AfterViewInit, EventEmitter, 
+import {Component, Input, OnInit, AfterViewInit, EventEmitter, OnDestroy
     HostListener, ViewEncapsulation} from '@angular/core';
+import {Subscription} from "rxjs/Subscription";
 import {EgGridDataSource} from './grid-data-source';
 import {EgIdlService} from '@eg/core/idl.service';
 import {EgOrgService} from '@eg/core/org.service';
 import {Pager} from '@eg/share/util/pager';
 import {EgGridService, EgGridColumn, EgGridColumnSet, EgGridToolbarButton, 
-    EgGridToolbarAction} from '@eg/share/grid/grid.service';
+    EgGridRowSelector, EgGridToolbarAction} from '@eg/share/grid/grid.service';
 
 @Component({
   selector: 'eg-grid',
@@ -15,7 +16,7 @@ import {EgGridService, EgGridColumn, EgGridColumnSet, EgGridToolbarButton,
   encapsulation: ViewEncapsulation.None
 })
 
-export class EgGridComponent implements OnInit, AfterViewInit {
+export class EgGridComponent implements OnInit, AfterViewInit, OnDestroy {
 
     @Input() dataSource: EgGridDataSource;
     @Input() idlClass: string;
@@ -26,16 +27,17 @@ export class EgGridComponent implements OnInit, AfterViewInit {
 
     pager: Pager;
     columnSet: EgGridColumnSet;
-    selector: {[idx:number] : boolean};
+    rowSelector: EgGridRowSelector;
     onRowDblClick$: EventEmitter<any>;
     onRowClick$: EventEmitter<any>;
     toolbarButtons: EgGridToolbarButton[];
     toolbarActions: EgGridToolbarAction[];
     lastSelectedIndex: any;
+    pageChanges: Subscription;
     
     constructor(private gridSvc: EgGridService) {
         this.pager = new Pager();
-        this.selector = {};
+        this.rowSelector = new EgGridRowSelector();
         this.pager.limit = 10; // TODO config
         this.onRowDblClick$ = new EventEmitter<any>();
         this.onRowClick$ = new EventEmitter<any>();
@@ -56,15 +58,41 @@ export class EgGridComponent implements OnInit, AfterViewInit {
         this.gridSvc.getColumnsConfig(this.persistKey)
         .then(conf => this.columnSet.applyColumnSettings(conf))
         .then(ok => this.dataSource.requestPage(this.pager))
+        .then(ok => this.listenToPager())
+    }
+
+    ngOnDestroy() {
+        this.dontListenToPager();
+    }
+
+    // Subscribe or unsubscribe to page-change events from the pager.
+    listenToPager() {
+        if (this.pageChanges) return;
+        this.pageChanges = this.pager.onChange$.subscribe(
+            val => this.dataSource.requestPage(this.pager));
+    }
+    
+    dontListenToPager() {
+        if (!this.pageChanges) return;
+        this.pageChanges.unsubscribe();
+        this.pageChanges = null
     }
 
     // Grid keyboard navigation handlers.
     @HostListener('window:keydown', ['$event']) onKeyDown(evt: KeyboardEvent) {
         if (evt.key == 'ArrowUp') {
             this.selectPreviousRow();
+
         } else if (evt.key == 'ArrowDown') {
             this.selectNextRow();
+
+        } else if (evt.key == 'ArrowLeft') {
+            this.toPrevPage().then(ok => this.selectFirstRow(), err => {});
+
         } else if (evt.key == 'ArrowRight') {
+            this.toNextPage().then(ok => this.selectFirstRow(), err => {});
+
+        } else if (evt.key == 'Enter') {
             if (this.lastSelectedIndex)
                 this.onRowDblClick(this.getRowByIndex(this.lastSelectedIndex));
         }
@@ -106,9 +134,8 @@ export class EgGridComponent implements OnInit, AfterViewInit {
     // currently visible in the grid display.
     getSelectedRows(): any[] {
         let selected = [];
-        Object.keys(this.selector).forEach(index => {
-            let row = this.dataSource.data.filter(
-                r => this.getRowIndex(r) == index)[0];
+        this.rowSelector.selected().forEach(index => {
+            let row = this.getRowByIndex(index);
             if (row) selected.push(row);
         });
         return selected;
@@ -138,61 +165,74 @@ export class EgGridComponent implements OnInit, AfterViewInit {
     }
 
     selectOneRow(index: any) {
-        this.selector = {};
-        this.selector[index] = true;
+        this.rowSelector.clear();
+        this.rowSelector.select(index);
         this.lastSelectedIndex = index;
     }
 
     // selects or deselects an item, without affecting the others.
     // returns true if the item is selected; false if de-selected.
     toggleSelectOneRow(index: any) {
-        if (this.selector[index]) {
-            delete this.selector[index];
+        if (this.rowSelector.contains(index)) {
+            this.rowSelector.deselect(index);
             return false;
         } 
 
-        this.selector[index] = true;
+        this.rowSelector.select(index);
         return true;
     }
 
-    
+    selectRowByPos(pos: number) {
+        let row = this.dataSource.data[pos];
+        if (row) this.selectOneRow(this.getRowIndex(row));
+    }
 
     selectPreviousRow() {
         if (!this.lastSelectedIndex) return;
         let pos = this.getRowPosition(this.lastSelectedIndex);
-        if (pos == 0) return;
         if (pos == this.pager.offset) {
-            // Request the previous page of data
-            this.pager.decrement();
-            this.dataSource.requestPage(this.pager)
-            .then(ok => {
-                let row = this.dataSource.data[pos - 1];
-                if (row) this.selectOneRow(this.getRowIndex(row));
-            });
+            this.toPrevPage().then(ok => this.selectLastRow(), err => {});
         } else {
-            let row = this.dataSource.data[pos - 1];
-            this.selectOneRow(this.getRowIndex(row));
+            this.selectRowByPos(pos - 1);
         }
     }
 
     selectNextRow() {
         if (!this.lastSelectedIndex) return;
         let pos = this.getRowPosition(this.lastSelectedIndex);
-
         if (pos == (this.pager.offset + this.pager.limit - 1)) {
-            // Request the next page of data
-            this.pager.increment();
-            this.dataSource.requestPage(this.pager)
-            .then(ok => {
-                let row = this.dataSource.data[pos + 1];
-                if (row) this.selectOneRow(this.getRowIndex(row));
-            });
+            this.toNextPage().then(ok => this.selectFirstRow(), err => {});
         } else {
-            let row = this.dataSource.data[pos + 1];
-            if (row) this.selectOneRow(this.getRowIndex(row));
+            this.selectRowByPos(pos + 1);
         }
     }
 
+    selectFirstRow() {
+        this.selectRowByPos(this.pager.offset);
+    }
+
+    selectLastRow() {
+        this.selectRowByPos(this.pager.offset + this.pager.limit - 1);
+    }
+
+    toPrevPage(): Promise<any> {
+        if (this.pager.isFirstPage()) return Promise.reject('on first');
+        // temp ignore pager events since we're calling requestPage manually.
+        this.dontListenToPager();
+        this.pager.decrement();
+        this.listenToPager();
+        return this.dataSource.requestPage(this.pager);
+    }
+
+    toNextPage(): Promise<any> {
+        if (this.pager.isLastPage()) return Promise.reject('on last');
+        // temp ignore pager events since we're calling requestPage manually.
+        this.dontListenToPager();
+        this.pager.increment();
+        this.listenToPager();
+        return this.dataSource.requestPage(this.pager);
+    }
 }
 
 
+
index 1491454..17703f3 100644 (file)
@@ -285,3 +285,44 @@ export class EgGridToolbarButton {
     action: () => any;
 }
 
+export class EgGridRowSelector {
+    indexes: {[string:string] : boolean};
+
+    constructor() {
+        this.clear();
+    }
+
+    // Returns true if all of the requested indexes exist in the selector.
+    contains(index: string | string[]): boolean {
+        let indexes = [].concat(index);
+        let selected = Object.keys(this.indexes);
+        for (let i = 0; i < indexes.length; i++) { // early exit
+            if (!selected.includes(indexes[i]))
+                return false; 
+        }
+        return true;
+    }
+
+    select(index: string | string[]) {
+        let indexes = [].concat(index);
+        indexes.forEach(i => this.indexes[i] = true);
+    }
+
+    deselect(index: string | string[]) {
+        let indexes = [].concat(index);
+        indexes.forEach(i => delete this.indexes[i]);
+    }
+
+    selected() {
+        return Object.keys(this.indexes);
+    }
+
+    isEmpty(): boolean {
+        return this.selected().length == 0;
+    }
+
+    clear() {
+        this.indexes = {};
+    }
+}
+
index 524e178..6d420b8 100644 (file)
@@ -10,6 +10,7 @@ export class Pager {
     onChange$: EventEmitter<number>;
 
     constructor() {
+        this.resultCount = null;
         this.onChange$ = new EventEmitter<number>();
     }
 
@@ -49,6 +50,7 @@ export class Pager {
     }
 
     pageCount(): number {
+        if (this.resultCount === null) return -1;
         let pages = this.resultCount / this.limit;
         if (Math.floor(pages) < pages)
             pages = Math.floor(pages) + 1;
index 91d37a5..4f19a2c 100644 (file)
@@ -22,6 +22,8 @@ export class BillingTypeComponent implements OnInit {
     @ViewChild('successString') successString: EgStringComponent;
     @ViewChild('createString') createString: EgStringComponent;
     contextOrg: EgIdlObject;
+    createBillingType: () => void;
+    deleteSelected: (rows: any) => void;
 
     constructor(
         private org: EgOrgService,
@@ -32,30 +34,11 @@ export class BillingTypeComponent implements OnInit {
         this.dataSource = new EgGridDataSource();
     }
 
-
     orgOnChange(org: EgIdlObject) {
-        console.log('orgOnChange called ' + org.id());
         this.contextOrg = org;
         this.btGrid.reload();
     }
 
-    createBillingType() {
-        this.btEditDialog.mode = 'create';
-        this.btEditDialog.open().then(
-            ok => {
-                this.createString.current()
-                    .then(str => this.toast.success(str));
-                this.btGrid.reload();
-            },
-            err => { }
-        );
-    }
-
-    deleteSelected(billingTypes) {
-        console.log('deleting...');
-        console.log(billingTypes);
-    }
-
     ngOnInit() {
         this.contextOrg = this.org.get(this.auth.user().ws_ou());
 
@@ -87,6 +70,27 @@ export class BillingTypeComponent implements OnInit {
                 );
             }
         );
+
+        this.createBillingType = () => {
+            this.btEditDialog.mode = 'create';
+            this.btEditDialog.open().then(
+                ok => {
+                    this.createString.current()
+                    .then(str => this.toast.success(str));
+                    this.btGrid.reload();
+                },
+                err => { }
+            );
+        }
+
+        this.deleteSelected = (billingTypes) => {
+            billingTypes.forEach(bt => bt.isdeleted(true));
+            this.pcrud.autoApply(billingTypes).subscribe(
+                val => console.debug('deleted: ' + val),
+                err => {},
+                ()  => this.btGrid.reload()
+            );
+        }
     }
 }