--- /dev/null
+<div *ngIf="isVisible" class="eg-grid-column-width-config">
+ <div class="eg-grid-row">
+ <div class="eg-grid-column-width-header" i18n>Expand</div>
+ <div *ngFor="let col of columnSet.displayColumns()"
+ class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
+ <a (click)="expandColumn(col)" title="Expand Column" i18n-title>
+ <span class="material-icons eg-grid-column-width-icon">call_made</span>
+ </a>
+ </div>
+ </div>
+ <div class="eg-grid-row">
+ <div class="eg-grid-column-width-header" i18n>Shrink</div>
+ <div *ngFor="let col of columnSet.displayColumns()"
+ class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
+ <a (click)="shrinkColumn(col)" title="Shrink Column" i18n-title>
+ <span class="material-icons eg-grid-column-width-icon">call_received</span>
+ </a>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, Input, OnInit, Host} from '@angular/core';
+import {EgGridService, EgGridColumn, EgGridColumnSet} from './grid.service';
+import {EgGridDataSource} from './grid-data-source';
+import {EgGridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-column-width',
+ templateUrl: './grid-column-width.component.html'
+})
+
+export class EgGridColumnWidthComponent implements OnInit {
+
+ @Input() columnSet: EgGridColumnSet;
+ isVisible: boolean;
+
+ constructor(
+ private gridSvc: EgGridService,
+ @Host() private grid: EgGridComponent
+ ) { }
+
+ ngOnInit() {
+ this.isVisible = false;
+ }
+
+ expandColumn(col: EgGridColumn) {
+ col.flex++;
+ }
+
+ shrinkColumn(col: EgGridColumn) {
+ if (col.flex > 1) col.flex--;
+ }
+
+}
+
@Input() path: string;
@Input() label: string;
@Input() flex: number;
+ // is this the index field?
+ @Input() index: boolean;
@Input() visible: boolean;
@Input() hidden: boolean;
- @Input() pkey: boolean;
@Input() sortable: boolean;
@Input() multiSortable: boolean;
@Input() cellTemplate: TemplateRef<any>;
col.path = this.path;
col.label = this.label;
col.flex = this.flex;
- col.hidden = this.hidden;
+ col.hidden = this.hidden === true;
+ col.isIndex = this.index === true;
col.cellTemplate = this.cellTemplate;
- col.isPkey = this.pkey;
col.isSortable = this.sortable;
col.isMultiSortable = this.multiSortable;
this.grid.columnSet.add(col);
}
// called on initial component load and user action (e.g. paging, sorting).
- requestPage(pager: Pager) {
+ requestPage(pager: Pager): Promise<any> {
- // see if the page of data is already present in the data
- if (this.getPageOfRows(pager).length > 0) return;
-
- if (this.allRowsRetrieved) return;
-
- if (!this.getRows) return;
+ if (
+ // already have the current page
+ this.getPageOfRows(pager).length > 0
+ // already have all data
+ || this.allRowsRetrieved
+ // have no way to get more data.
+ || !this.getRows
+ ) {
+ return Promise.resolve();
+ }
- let idx = pager.offset;
- this.getRows(pager, this.sort).subscribe(
- row => this.data[idx++] = row,
- err => console.error(`grid getRows() error ${err}`),
- () => this.checkAllRetrieved(pager, idx)
- );
+ return new Promise((resolve, reject) => {
+ let idx = pager.offset;
+ return this.getRows(pager, this.sort).subscribe(
+ row => this.data[idx++] = row,
+ err => {
+ console.error(`grid getRows() error ${err}`);
+ reject(err);
+ },
+ () => {
+ this.checkAllRetrieved(pager, idx);
+ resolve();
+ }
+ );
+ });
}
// See if the last getRows() call resulted in the final set of data.
--- /dev/null
+import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
+import {EgGridService, EgGridToolbarAction} from './grid.service';
+import {EgGridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-toolbar-action',
+ template: '<ng-template></ng-template>'
+})
+
+export class EgGridToolbarActionComponent implements OnInit {
+
+ // Note most input fields should match class fields for EgGridColumn
+ @Input() label: string;
+ @Input() action: (rows: any[]) => any;
+
+ // get a reference to our container grid.
+ constructor(
+ private gridSvc: EgGridService,
+ @Host() private grid: EgGridComponent) {
+ }
+
+ ngOnInit() {
+
+ if (!this.grid) {
+ console.warn('EgGridToolbarActionComponent needs a [grid]');
+ return;
+ }
+
+ let action = new EgGridToolbarAction();
+ action.label = this.label;
+ action.action = this.action;
+
+ this.grid.toolbarActions.push(action);
+ }
+}
+
<!-- push everything else to the right -->
<div class="flex-1"></div>
- <div class="btn-toolbar">
+ <div ngbDropdown class="ml-1" placement="bottom-right">
+ <button ngbDropdownToggle [disabled]="!toolbarActions.length"
+ class="btn btn-light no-dropdown-caret">
+ <span title="Actions For Selected Rows"
+ i18n-title class="material-icons">playlist_add_check</span>
+ </button>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" (click)="performAction(action)"
+ *ngFor="let action of toolbarActions">
+ <span class="ml-2">{{action.label}}</span>
+ </a>
+ </div>
+ </div>
+
+ <div class="btn-toolbar ml-1">
<div class="btn-grp">
<button [disabled]="pager.isFirstPage()" type="button" class="btn btn-light" (click)="pager.toFirst()">
<span title="First Page" i18n-title class="material-icons">first_page</span>
</eg-grid-column-config>
<div ngbDropdown class="ml-1" placement="bottom-right">
<button ngbDropdownToggle class="btn btn-light no-dropdown-caret">
- <span title="Show Grid Options" i18n-title class="material-icons">arrow_drop_down</span>
+ <span title="Show Grid Options" i18n-title class="material-icons">settings</span>
</button>
<div class="dropdown-menu" ngbDropdownMenu>
<a class="dropdown-item label-with-material-icon"
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, Input, OnInit, Host} from '@angular/core';
import {EgGridDataSource} from './grid-data-source';
import {Pager} from '@eg/share/util/pager';
-import {EgGridService, EgGridColumn, EgGridColumnSet, EgGridToolbarButton}
- from '@eg/share/grid/grid.service';
+import {EgGridService, EgGridColumn, EgGridColumnSet, EgGridToolbarButton,
+ EgGridToolbarAction} from '@eg/share/grid/grid.service';
+import {EgGridComponent} from './grid.component';
import {EgDialogComponent} from '@eg/share/dialog/dialog.component';
import {EgGridColumnWidthComponent} from './grid-column-width.component';
@Input() dataSource: EgGridDataSource;
@Input() pager: Pager;
@Input() toolbarButtons: EgGridToolbarButton[];
+ @Input() toolbarActions: EgGridToolbarAction[];
@Input() columnSet: EgGridColumnSet;
@Input() colWidthConfig: EgGridColumnWidthComponent;
@Input() persistKey: string;
- constructor(private gridSvc: EgGridService) {}
+ constructor(private gridSvc: EgGridService,
+ @Host() private grid: EgGridComponent) {
+ }
ngOnInit() {
err => console.error(`Error saving columns: ${err}`)
);
}
+
+ performAction(action: EgGridToolbarAction) {
+ action.action(this.grid.getSelectedRows());
+ }
}
.eg-grid-body-row {
}
+.eg-grid-body-row.selected {
+ color: #000;
+ background-color: rgb(201, 221, 225);
+ border-bottom: 1px solid #888;
+}
+
.eg-grid-header-cell {
font-weight: bold;
}
<div class="eg-grid">
<eg-grid-toolbar
[dataSource]="dataSource" [pager]="pager"
- [toolbarButtons]="toolbarButtons" [columnSet]="columnSet"
+ [columnSet]="columnSet"
+ [toolbarButtons]="toolbarButtons" [toolbarActions]="toolbarActions"
[colWidthConfig]="colWidthConfig" persistKey="{{persistKey}}">
</eg-grid-toolbar>
<eg-grid-header [columnSet]="columnSet" [dataSource]="dataSource"></eg-grid-header>
<eg-grid-column-width #colWidthConfig [columnSet]="columnSet"></eg-grid-column-width>
<div class="eg-grid-row eg-grid-body-row"
- [ngClass]="{'eg-grid-row-selected': selector[idx]}"
+ [ngClass]="{'selected': selector[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[idx]">
+ <input type='checkbox' [(ngModel)]="selector[getRowIndex(row)]">
</div>
<div class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
{{pager.rowNumber(idx)}}
</div>
<div class="eg-grid-cell eg-grid-body-cell" [ngStyle]="{flex:col.flex}"
(dblclick)="onRowDblClick(row)"
+ (click)="onRowClick($event, row, idx)"
*ngFor="let col of columnSet.displayColumns()">
{{getDisplayValue(row, col)}}
</div>
-import {Component, Input, OnInit, EventEmitter, ViewEncapsulation} from '@angular/core';
+import {Component, Input, OnInit, AfterViewInit, EventEmitter,
+ HostListener, ViewEncapsulation} from '@angular/core';
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}
- from '@eg/share/grid/grid.service';
+import {EgGridService, EgGridColumn, EgGridColumnSet, EgGridToolbarButton,
+ EgGridToolbarAction} from '@eg/share/grid/grid.service';
@Component({
selector: 'eg-grid',
encapsulation: ViewEncapsulation.None
})
-export class EgGridComponent implements OnInit {
+export class EgGridComponent implements OnInit, AfterViewInit {
@Input() dataSource: EgGridDataSource;
@Input() idlClass: string;
@Input() isSortable: boolean;
@Input() isMultiSortable: boolean;
@Input() persistKey: string;
+ @Input() disableMultiSelect: boolean;
pager: Pager;
columnSet: EgGridColumnSet;
selector: {[idx:number] : boolean};
onRowDblClick$: EventEmitter<any>;
+ onRowClick$: EventEmitter<any>;
toolbarButtons: EgGridToolbarButton[];
+ toolbarActions: EgGridToolbarAction[];
+ lastSelectedIndex: any;
constructor(private gridSvc: EgGridService) {
this.pager = new Pager();
this.selector = {};
this.pager.limit = 10; // TODO config
this.onRowDblClick$ = new EventEmitter<any>();
+ this.onRowClick$ = new EventEmitter<any>();
this.toolbarButtons = [];
+ this.toolbarActions = [];
}
ngOnInit() {
this.columnSet = new EgGridColumnSet(this.idlClass);
- this.columnSet.isSortable = this.isSortable;
- this.columnSet.isMultiSortable = this.isMultiSortable;
+ this.columnSet.isSortable = this.isSortable === true;
+ this.columnSet.isMultiSortable = this.isMultiSortable === true;
this.gridSvc.generateColumns(this.columnSet);
+ }
+
+ // Apply column configuation and fetch data after our child
+ // components have told us what columns we have available.
+ ngAfterViewInit() {
this.gridSvc.getColumnsConfig(this.persistKey)
.then(conf => this.columnSet.applyColumnSettings(conf))
.then(ok => this.dataSource.requestPage(this.pager))
}
+ @HostListener('window:keydown', ['$event']) onKeyDown(evt: KeyboardEvent) {
+ if (evt.key == 'ArrowUp') {
+ this.selectPreviousRow();
+ } else if (evt.key == 'ArrowDown') {
+ this.selectNextRow();
+ }
+ }
+
reload() {
this.pager.offset = 0;
- this.dataSource.data = [];
+ this.dataSource.reset();
this.dataSource.requestPage(this.pager);
}
return this.gridSvc.getRowColumnValue(row, col);
}
+ // Returns the index (AKA pkey) value for the row
+ getRowIndex(row: any): any {
+ return this.gridSvc.getRowIndex(row, this.columnSet);
+ }
+
+ // Returns the array position of the row-by-index in the
+ // dataSource array.
+ getRowPosition(index: any): number {
+ // for-loop for early exit
+ for (let idx = 0; idx < this.dataSource.data.length; idx++) {
+ if (index == this.getRowIndex(this.dataSource.data[idx]))
+ return idx;
+ }
+ }
+
+ // Returns all selected rows, regardless of whether they are
+ // 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];
+ if (row) selected.push(row);
+ });
+ return selected;
+ }
+
onRowDblClick(row: any) {
this.onRowDblClick$.emit(row);
}
+ onRowClick($event: any, row: any, idx: number) {
+ let index = this.getRowIndex(row);
+
+ if (this.disableMultiSelect) {
+ this.selectOneRow(index);
+ } else if ($event.ctrlKey || $event.metaKey /* mac command */) {
+ if (this.toggleSelectOneRow(index))
+ this.lastSelectedIndex = index;
+
+ } else if ($event.shiftKey) {
+ // TODO shift range click
+
+ } else {
+ this.selectOneRow(index);
+ }
+
+ this.onRowClick$.emit(row);
+ }
+
+ selectOneRow(index: any) {
+ this.selector = {};
+ this.selector[index] = true;
+ 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];
+ return false;
+ }
+
+ this.selector[index] = true;
+ return true;
+ }
+
+
+
+ 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));
+ });
+ } else {
+ let row = this.dataSource.data[pos - 1];
+ this.selectOneRow(this.getRowIndex(row));
+ }
+ }
+
+ 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));
+ });
+ } else {
+ let row = this.dataSource.data[pos + 1];
+ if (row) this.selectOneRow(this.getRowIndex(row));
+ }
+ }
+
}
import {NgModule} from '@angular/core';
import {EgCommonModule} from '@eg/common.module';
-//import {EgDialogComponent} from '@eg/share/dialog/dialog.component';
import {EgGridComponent} from './grid.component';
import {EgGridColumnComponent} from './grid-column.component';
import {EgGridHeaderComponent} from './grid-header.component';
import {EgGridToolbarComponent} from './grid-toolbar.component';
import {EgGridService} from './grid.service';
import {EgGridToolbarButtonComponent} from './grid-toolbar-button.component';
+import {EgGridToolbarActionComponent} from './grid-toolbar-action.component';
import {EgGridColumnConfigComponent} from './grid-column-config.component';
import {EgGridColumnWidthComponent} from './grid-column-width.component';
EgGridHeaderComponent,
EgGridToolbarComponent,
EgGridToolbarButtonComponent,
+ EgGridToolbarActionComponent,
EgGridColumnConfigComponent,
EgGridColumnWidthComponent
],
// public components
EgGridComponent,
EgGridColumnComponent,
- EgGridToolbarButtonComponent
+ EgGridToolbarButtonComponent,
+ EgGridToolbarActionComponent
],
providers: [
EgGridService
) {
}
- getRowPkey(row: any, columnSet: EgGridColumnSet): any {
- let col = columnSet.pkeyColumn;
- if (!col) throw new Error('grid pkey column required');
+ getRowIndex(row: any, columnSet: EgGridColumnSet): any {
+ let col = columnSet.indexColumn;
+ if (!col) throw new Error('grid index column required');
return this.getRowColumnValue(row, col);
}
col.label = field.label || field.name;
col.idlFieldDef = field;
if (field.name == this.idl.classes[columnSet.idlClass].pkey)
- col.isPkey = true;
+ col.isIndex = true;
columnSet.add(col);
});
}
idlClass: string;
idlFieldDef: any;
cellTemplate: TemplateRef<any>;
- isPkey: boolean;
+ isIndex: boolean;
isDragTarget: boolean;
isSortable: boolean;
isMultiSortable: boolean;
export class EgGridColumnSet {
columns: EgGridColumn[];
idlClass: string;
- pkeyColumn: EgGridColumn;
+ indexColumn: EgGridColumn;
isSortable: boolean;
isMultiSortable: boolean;
stockVisible: string[];
// avoid dupes
if (this.getColByName(col.name)) return;
- if (col.isPkey) this.pkeyColumn = col;
+ 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';
}
}
+// Actions apply to specific rows
+export class EgGridToolbarAction {
+ label: string;
+ action: (rows: any[]) => any;
+}
+
+// Buttons are global actions
export class EgGridToolbarButton {
label: string;
action: () => any;
}
-
[isSortable]="true" persistKey="admin.server.config.billing_type">
<eg-grid-toolbar-button label="New Billing Type" i18n-label [action]="createBillingType">
</eg-grid-toolbar-button>
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label [action]="deleteSelected">
+ </eg-grid-toolbar-action>
</eg-grid>
<fm-record-editor #btEditDialog idlClass="cbt" requiredFields="name,org_unit">
@ViewChild('successString') successString: EgStringComponent;
@ViewChild('createString') createString: EgStringComponent;
createBillingType: () => any;
+ //deleteSelectedBillingTypes: (bts: any[]) => any;
constructor(
private pcrud: EgPcrudService,
}
}
+ deleteSelected(billingTypes) {
+ console.log('deleting...');
+ console.log(billingTypes);
+ }
+
ngOnInit() {
this.dataSource.getRows = (pager: Pager, sort: any[]) => {