LP#1626157 More grid / csv
authorBill Erickson <berickxx@gmail.com>
Sun, 13 May 2018 22:03:19 +0000 (18:03 -0400)
committerBill Erickson <berickxx@gmail.com>
Sun, 13 May 2018 22:03:19 +0000 (18:03 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
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.ts

index 60499a9..2bd0e3f 100644 (file)
@@ -80,7 +80,9 @@
         <span class="ml-2" i18n>Reset Columns</span>
       </a>
       <a class="dropdown-item label-with-material-icon" 
-        (click)="downloadCsv()">
+        (click)="generateCsvExportUrl($event)"
+        [download]="csvExportFileName"
+        [href]="csvExportUrl">
         <span class="material-icons">cloud_download</span>
         <span class="ml-2" i18n>Download CSV</span>
       </a>
index 0e8c745..d32fee6 100644 (file)
@@ -1,4 +1,5 @@
 import {Component, Input, OnInit, Host} from '@angular/core';
+import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
 import {Pager} from '@eg/share/util/pager';
 import {EgGridColumn, EgGridColumnSet, EgGridToolbarButton, 
     EgGridToolbarAction, EgGridContext, EgGridDataSource} from '@eg/share/grid/grid';
@@ -14,7 +15,11 @@ export class EgGridToolbarComponent implements OnInit {
     @Input() gridContext: EgGridContext;
     @Input() colWidthConfig: EgGridColumnWidthComponent;
 
-    constructor() {}
+    csvExportInProgress: boolean;
+    csvExportUrl: SafeUrl;
+    csvExportFileName: string;
+
+    constructor(private sanitizer: DomSanitizer) {}
 
     ngOnInit() {}
 
@@ -34,9 +39,45 @@ export class EgGridToolbarComponent implements OnInit {
         action.action(this.gridContext.getSelectedRows());
     }
 
-    downloadCsv() {
-    }
+    generateCsvExportUrl($event) {
+
+        if (this.csvExportInProgress) {
+            // This is secondary href click handler.  Give the
+            // browser a moment to start the download, then reset
+            // the CSV download attributes / state.
+            setTimeout(() => {
+                this.csvExportUrl = null;
+                this.csvExportFileName = ''; 
+                this.csvExportInProgress = false;
+               }, 500
+            );
+            return;
+        } 
+
+        this.csvExportInProgress = true;
 
+        // let the file name describe the grid
+        this.csvExportFileName = (
+            this.gridContext.mainLabel || 
+            this.gridContext.persistKey || 
+            'eg_grid_data'
+        ).replace(/\s+/g, '_') + '.csv';
+
+        this.gridContext.gridToCsv().then(csv => {
+            console.log(csv);
+            var blob = new Blob([csv], {type : 'text/plain'});
+            let win: any = window;
+            this.csvExportUrl = this.sanitizer.bypassSecurityTrustUrl(
+                (win.URL || win.webkitURL).createObjectURL(blob)
+            );
+            
+            // Fire the 2nd click event now that the browser has
+            // information on how to download the CSV file.
+            setTimeout(() => $event.target.click());
+        });
+
+        $event.preventDefault();
+    }
 }
 
 
index 624b284..f2949b3 100644 (file)
@@ -17,6 +17,7 @@ import {EgGridContext, EgGridColumn, EgGridDataSource} from './grid';
 
 export class EgGridComponent implements OnInit, AfterViewInit, OnDestroy {
 
+    @Input() mainLabel: string;
     @Input() dataSource: EgGridDataSource;
     @Input() idlClass: string;
     @Input() isSortable: boolean;
@@ -41,6 +42,7 @@ export class EgGridComponent implements OnInit, AfterViewInit, OnDestroy {
     }
 
     ngOnInit() {
+        this.context.mainLabel = this.mainLabel;
         this.context.idlClass = this.idlClass;
         this.context.dataSource = this.dataSource;
         this.context.persistKey = this.persistKey
index 4d1e18d..3a5c317 100644 (file)
@@ -10,6 +10,8 @@ import {EgStoreService} from '@eg/core/store.service';
 import {EgFormatService} from '@eg/share/util/format.service';
 import {Pager} from '@eg/share/util/pager';
 
+const MAX_ALL_ROW_COUNT = 10000;
+
 export class EgGridContext {
 
     pager: Pager;
@@ -25,6 +27,7 @@ export class EgGridContext {
     toolbarActions: EgGridToolbarAction[];
     lastSelectedIndex: any;
     pageChanges: Subscription;
+    mainLabel: string;
 
     // Services injected by our grid component
     idl: EgIdlService;
@@ -130,6 +133,14 @@ export class EgGridContext {
         return this.format.transform({value: val, datatype: col.datatype});
     }
 
+    getColumnTextContent(row: any, col: EgGridColumn): string {
+        if (col.cellTemplate) {
+            // TODO
+        } else {
+            return this.getRowColumnValue(row, col);
+        }
+    }
+
     selectOneRow(index: any) {
         this.rowSelector.clear();
         this.rowSelector.select(index);
@@ -199,6 +210,75 @@ export class EgGridContext {
         return this.dataSource.requestPage(this.pager);
     }
 
+    getAllRows(): Promise<any> {
+        let pager = new Pager();
+        pager.offset = 0;
+        pager.limit = MAX_ALL_ROW_COUNT;
+        return this.dataSource.requestPage(pager);
+    }
+
+    // Returns a key/value pair object of visible column data as text.
+    getRowAsFlatText(row: any): any {
+        let flatRow = {};
+        this.columnSet.displayColumns().forEach(col => {
+            flatRow[col.name] = 
+                this.getColumnTextContent(row, col);
+        });
+        return flatRow;
+    }
+
+    getAllRowsAsText(): Observable<any> {
+        return Observable.create(observer => {
+            this.getAllRows().then(ok => {
+                this.dataSource.data.forEach(row => {
+                    observer.next(this.getRowAsFlatText(row));
+                })
+                observer.complete();
+            });
+        });
+    }
+
+    gridToCsv(): Promise<string> {
+
+        let csvStr = '';
+        let columns = this.columnSet.displayColumns();
+
+        // CSV header
+        columns.forEach(col => {
+            csvStr += this.valueToCsv(col.label),
+            csvStr += ',';
+        });
+
+        csvStr = csvStr.replace(/,$/,'\n');
+
+        return new Promise(resolve => {
+            this.getAllRowsAsText().subscribe(
+                row => {
+                    columns.forEach(col => {
+                        csvStr += this.valueToCsv(row[col.name]);
+                        csvStr += ',';
+                    });
+                    csvStr = csvStr.replace(/,$/,'\n');
+                },
+                err => {},
+                ()  => resolve(csvStr)
+            );
+        });
+    }
+
+
+    // prepares a string for inclusion within a CSV document
+    // by escaping commas and quotes and removing newlines.
+    valueToCsv(str: string): string {
+        str = ''+str;
+        if (!str) return '';
+        str = str.replace(/\n/g, '');
+        if (str.match(/\,/) || str.match(/"/)) {                                     
+            str = str.replace(/"/g, '""');
+            str = '"' + str + '"';                                           
+        } 
+        return str;
+    }
 
     generateColumns() {
 
@@ -510,8 +590,7 @@ export class EgGridDataSource {
     requestPage(pager: Pager): Promise<any> {
 
         if (
-            // already have the current page
-            this.getPageOfRows(pager).length > 0 
+            this.getPageOfRows(pager).length == pager.limit
             // already have all data
             || this.allRowsRetrieved
             // have no way to get more data.