tabindex=1 so the grid body can capture keyboard events.
-->
<div class="eg-grid-body" tabindex="1" (keydown)="onGridKeyDown($event)">
- <div class="eg-grid-row eg-grid-body-row {{context.rowClassCallback(row)}}"
+ <div role="row" class="eg-grid-row eg-grid-body-row {{context.rowClassCallback(row)}}"
[ngClass]="{'selected': context.rowSelector.contains(context.getRowIndex(row))}"
*ngFor="let row of context.dataSource.getPageOfRows(context.pager); let idx = index">
</ng-container>
</ng-container>
</div>
- <div class="eg-grid-cell eg-grid-body-cell" [ngStyle]="{flex:col.flex}"
+ <div role="gridcell" class="eg-grid-cell eg-grid-body-cell"
+ [ngStyle]="{flex:col.flex}"
[ngClass]="{'eg-grid-cell-overflow': context.overflowCells}"
(dblclick)="onRowDblClick(row)"
(click)="onRowClick($event, row, idx)"
onGridKeyDown(evt: KeyboardEvent) {
switch (evt.key) {
case 'ArrowUp':
- this.context.selectPreviousRow();
+ if (evt.shiftKey) {
+ // Extend selection up one row
+ this.context.selectMultiRowsPrevious();
+ } else {
+ this.context.selectPreviousRow();
+ }
evt.stopPropagation();
break;
case 'ArrowDown':
- this.context.selectNextRow();
+ if (evt.shiftKey) {
+ // Extend selection down one row
+ this.context.selectMultiRowsNext();
+ } else {
+ this.context.selectNextRow();
+ }
evt.stopPropagation();
break;
case 'ArrowLeft':
+ case 'PageUp':
this.context.toPrevPage()
.then(ok => this.context.selectFirstRow(), err => {});
evt.stopPropagation();
break;
case 'ArrowRight':
+ case 'PageDown':
this.context.toNextPage()
.then(ok => this.context.selectFirstRow(), err => {});
evt.stopPropagation();
break;
+ case 'a':
+ // control-a means select all visible rows.
+ // For consistency, select all rows in the current page only.
+ if (evt.ctrlKey) {
+ this.context.rowSelector.clear();
+ this.context.selectRowsInPage();
+ evt.preventDefault();
+ }
+ break;
+
case 'Enter':
if (this.context.lastSelectedIndex) {
this.grid.onRowActivate.emit(
-<div class="eg-grid-row eg-grid-header-row">
+<div row="row" class="eg-grid-row eg-grid-header-row">
<ng-container *ngIf="!context.disableSelect">
- <div class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
- <input type='checkbox' (click)="handleBatchSelect($event)">
+ <div role="columnheader"
+ class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+ <input type='checkbox' (click)="handleBatchSelect($event)"
+ [(ngModel)]="batchRowCheckbox">
</div>
</ng-container>
- <div class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
+ <div role="columnheader"
+ class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
<span i18n="number|Row Number Header">#</span>
</div>
- <div *ngIf="context.rowFlairIsEnabled"
+ <div *ngIf="context.rowFlairIsEnabled"
+ role="columnheader"
class="eg-grid-cell eg-grid-header-cell eg-grid-flair-cell">
<span class="material-icons">notifications</span>
</div>
- <div *ngFor="let col of context.columnSet.displayColumns()"
- draggable="true"
+ <div role="columnheader"
+ *ngFor="let col of context.columnSet.displayColumns()"
+ draggable="true"
(dragstart)="dragColumn = col"
(drop)="onColumnDrop(col)"
(dragover)="onColumnDragEnter($event, col)"
(dragleave)="onColumnDragLeave($event, col)"
[ngClass]="{'dragover' : col.isDragTarget}"
class="eg-grid-cell eg-grid-header-cell" [ngStyle]="{flex:col.flex}">
- <a class="sortable label-with-material-icon" *ngIf="col.isSortable"
+ <a class="sortable label-with-material-icon" *ngIf="col.isSortable"
(click)="sortOneColumn(col)">
<span class="eg-grid-header-cell-sort-label">{{col.label}}</span>
<span class="material-icons eg-grid-header-cell-sort-arrow"
dragColumn: GridColumn;
+ batchRowCheckbox: boolean;
+
constructor() {}
- ngOnInit() {}
+ ngOnInit() {
+ this.context.selectRowsInPageEmitter.subscribe(
+ () => this.batchRowCheckbox = true
+ );
+ }
onColumnDragEnter($event: any, col: any) {
if (this.dragColumn && this.dragColumn.name !== col.name) {
}
selectAll() {
- const rows = this.context.dataSource.getPageOfRows(this.context.pager);
- const indexes = rows.map(r => this.context.getRowIndex(r));
- this.context.rowSelector.select(indexes);
+ this.context.selectRowsInPage();
}
allRowsAreSelected(): boolean {
<!-- buttons -->
<div class="btn-grp" *ngIf="gridContext.toolbarButtons.length">
- <button *ngFor="let btn of gridContext.toolbarButtons"
+ <button *ngFor="let btn of gridContext.toolbarButtons"
[disabled]="btn.disabled"
class="btn btn-outline-dark mr-1" (click)="btn.action()">
{{btn.label}}
</div>
<!-- checkboxes -->
- <div class="form-check form-check-inline"
+ <div class="form-check form-check-inline"
*ngIf="gridContext.toolbarCheckboxes.length">
<ng-container *ngFor="let cb of gridContext.toolbarCheckboxes">
<label class="form-check-label">
- <input class="form-check-input" type="checkbox"
+ <input class="form-check-input" type="checkbox"
(click)="cb.onChange($event.target.checked)"/>
{{cb.label}}
</label>
<!-- push everything else to the right -->
<div class="flex-1"></div>
+ <div class="font-sm font-italic d-flex flex-column-reverse mr-2">
+ {{gridContext.rowSelector.selected().length}} selected
+ </div>
<div ngbDropdown class="mr-1" placement="bottom-right">
<button ngbDropdownToggle [disabled]="!gridContext.toolbarActions.length"
class="btn btn-outline-dark no-dropdown-caret">
- <span title="Actions For Selected Rows" i18n-title
+ <span title="Actions For Selected Rows" i18n-title
class="material-icons mat-icon-in-button">playlist_add_check</span>
</button>
<div class="dropdown-menu" ngbDropdownMenu>
</div>
</div>
- <button [disabled]="gridContext.pager.isFirstPage()" type="button"
+ <button [disabled]="gridContext.pager.isFirstPage()" type="button"
class="btn btn-outline-dark mr-1" (click)="gridContext.pager.toFirst()">
- <span title="First Page" i18n-title
+ <span title="First Page" i18n-title
class="material-icons mat-icon-in-button">first_page</span>
</button>
- <button [disabled]="gridContext.pager.isFirstPage()" type="button"
+ <button [disabled]="gridContext.pager.isFirstPage()" type="button"
class="btn btn-outline-dark mr-1" (click)="gridContext.pager.decrement()">
- <span title="Previous Page" i18n-title
+ <span title="Previous Page" i18n-title
class="material-icons mat-icon-in-button">keyboard_arrow_left</span>
</button>
- <button [disabled]="gridContext.pager.isLastPage()" type="button"
+ <button [disabled]="gridContext.pager.isLastPage()" type="button"
class="btn btn-outline-dark mr-1" (click)="gridContext.pager.increment()">
- <span title="Next Page" i18n-title
+ <span title="Next Page" i18n-title
class="material-icons mat-icon-in-button">keyboard_arrow_right</span>
</button>
<!--
Hiding jump-to-last since there's no analog in the angularjs grid and
it has limited value since the size of the data set is often unknown.
- <button [disabled]="!gridContext.pager.resultCount || gridContext.pager.isLastPage()"
+ <button [disabled]="!gridContext.pager.resultCount || gridContext.pager.isLastPage()"
type="button" class="btn btn-outline-dark mr-1" (click)="gridContext.pager.toLast()">
- <span title="First Page" i18n-title
+ <span title="First Page" i18n-title
class="material-icons mat-icon-in-button">last_page</span>
</button>
-->
</span>
</button>
<div class="dropdown-menu" ngbDropdownMenu>
- <a class="dropdown-item"
+ <a class="dropdown-item"
*ngFor="let count of [5, 10, 25, 50, 100]"
(click)="gridContext.pager.setLimit(count)">
<span class="ml-2">{{count}}</span>
</div>
</div>
- <button type="button"
- class="btn btn-outline-dark mr-1"
+ <button type="button"
+ class="btn btn-outline-dark mr-1"
(click)="gridContext.overflowCells=!gridContext.overflowCells">
<span *ngIf="!gridContext.overflowCells"
- title="Expand Cells Vertically" i18n-title
+ title="Expand Cells Vertically" i18n-title
class="material-icons mat-icon-in-button">expand_more</span>
<span *ngIf="gridContext.overflowCells"
- title="Collaps Cells Vertically" i18n-title
+ title="Collaps Cells Vertically" i18n-title
class="material-icons mat-icon-in-button">expand_less</span>
</button>
</eg-grid-column-config>
<div ngbDropdown placement="bottom-right">
<button ngbDropdownToggle class="btn btn-outline-dark no-dropdown-caret">
- <span title="Show Grid Options" i18n-title
+ <span title="Show Grid Options" i18n-title
class="material-icons mat-icon-in-button">settings</span>
</button>
<div class="dropdown-menu" ngbDropdownMenu>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="columnConfDialog.open({size:'lg'})">
<span class="material-icons">build</span>
<span class="ml-2" i18n>Manage Columns</span>
</a>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="colWidthConfig.isVisible = !colWidthConfig.isVisible">
<span class="material-icons">compare_arrows</span>
<span class="ml-2" i18n>Manage Column Widths</span>
</a>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="saveGridConfig()">
<span class="material-icons">save</span>
<span class="ml-2" i18n>Save Grid Settings</span>
</a>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="gridContext.columnSet.reset()">
<span class="material-icons">restore</span>
<span class="ml-2" i18n>Reset Columns</span>
</a>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="generateCsvExportUrl($event)"
[download]="csvExportFileName"
[href]="csvExportUrl">
<div class="dropdown-divider"></div>
- <a class="dropdown-item label-with-material-icon"
+ <a class="dropdown-item label-with-material-icon"
(click)="col.visible=!col.visible" *ngFor="let col of gridContext.columnSet.columns">
<span *ngIf="col.visible" class="badge badge-success">✓</span>
<span *ngIf="!col.visible" class="badge badge-warning">✗</span>
-<div class="eg-grid">
+<div class="eg-grid" role="grid">
<eg-grid-toolbar
- [gridContext]="context"
+ [gridContext]="context"
[gridPrinter]="gridPrinter"
[colWidthConfig]="colWidthConfig">
</eg-grid-toolbar>
<eg-grid-column-width #colWidthConfig [gridContext]="context">
</eg-grid-column-width>
-
+
<eg-grid-print #gridPrinter [gridContext]="context">
</eg-grid-print>
/**
* Collection of grid related classses and interfaces.
*/
-import {TemplateRef} from '@angular/core';
+import {TemplateRef, EventEmitter} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {IdlService, IdlObject} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
defaultHiddenFields: string[];
overflowCells: 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;
org: OrgService;
}
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.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) {
}
}
+ // 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> {