igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
809 lines (740 loc) • 35.5 kB
text/typescript
import { Injectable } from '@angular/core';
import { first } from 'rxjs/operators';
import { IgxForOfDirective } from '../directives/for-of/for_of.directive';
import { GridType } from './common/grid.interface';
import {
NAVIGATION_KEYS,
ROW_COLLAPSE_KEYS,
ROW_EXPAND_KEYS,
SUPPORTED_KEYS,
HORIZONTAL_NAV_KEYS,
HEADER_KEYS,
ROW_ADD_KEYS,
PlatformUtil
} from '../core/utils';
import { GridKeydownTargetType, GridSelectionMode, FilterMode } from './common/enums';
import { IActiveNodeChangeEventArgs } from './common/events';
import { IgxGridGroupByRowComponent } from './grid/groupby-row.component';
import { IMultiRowLayoutNode } from './common/types';
import { SortingDirection } from '../data-operations/sorting-strategy';
export interface ColumnGroupsCache {
level: number;
visibleIndex: number;
}
export interface IActiveNode {
gridID?: string;
row: number;
column?: number;
level?: number;
mchCache?: ColumnGroupsCache;
layout?: IMultiRowLayoutNode;
}
/** @hidden */
export class IgxGridNavigationService {
public grid: GridType;
public _activeNode: IActiveNode = {} as IActiveNode;
public lastActiveNode: IActiveNode = {} as IActiveNode;
protected pendingNavigation = false;
public get activeNode() {
return this._activeNode;
}
public set activeNode(value: IActiveNode) {
this._activeNode = value;
}
constructor(protected platform: PlatformUtil) { }
public handleNavigation(event: KeyboardEvent) {
const key = event.key.toLowerCase();
if (NAVIGATION_KEYS.has(key)) {
event.stopPropagation();
}
if (this.grid.crudService.cell && NAVIGATION_KEYS.has(key)) {
return;
}
if (event.repeat && SUPPORTED_KEYS.has(key) || (key === 'tab' && this.grid.crudService.cell)) {
event.preventDefault();
}
if (event.repeat) {
setTimeout(() => this.dispatchEvent(event), 1);
} else {
this.dispatchEvent(event);
}
}
public dispatchEvent(event: KeyboardEvent) {
const key = event.key.toLowerCase();
if (!this.activeNode || !(SUPPORTED_KEYS.has(key) || (key === 'tab' && this.grid.crudService.cell)) &&
!this.grid.crudService.rowEditingBlocked && !this.grid.crudService.rowInEditMode) {
return;
}
const shift = event.shiftKey;
const ctrl = event.ctrlKey;
if (NAVIGATION_KEYS.has(key) && this.pendingNavigation) {
event.preventDefault();
return;
}
const type = this.isDataRow(this.activeNode.row) ? 'dataCell' :
this.isDataRow(this.activeNode.row, true) ? 'summaryCell' : 'groupRow';
if (this.emitKeyDown(type, this.activeNode.row, event)) {
return;
}
if (event.altKey) {
this.handleAlt(key, event);
return;
}
if ([' ', 'spacebar', 'space'].indexOf(key) === -1) {
this.grid.selectionService.keyboardStateOnKeydown(this.activeNode, shift, shift && key === 'tab');
}
const position = this.getNextPosition(this.activeNode.row, this.activeNode.column, key, shift, ctrl, event);
if (NAVIGATION_KEYS.has(key)) {
event.preventDefault();
this.navigateInBody(position.rowIndex, position.colIndex, (obj) => {
obj.target.activate(event);
this.grid.cdr.detectChanges();
});
}
this.grid.cdr.detectChanges();
}
public summaryNav(event: KeyboardEvent) {
if (this.grid.hasSummarizedColumns) {
this.horizontalNav(event, event.key.toLowerCase(), this.grid.dataView.length, 'summaryCell');
}
}
public headerNavigation(event: KeyboardEvent) {
const key = event.key.toLowerCase();
if (!HEADER_KEYS.has(key)) {
return;
}
event.preventDefault();
const ctrl = event.ctrlKey;
const shift = event.shiftKey;
const alt = event.altKey;
this.performHeaderKeyCombination(this.currentActiveColumn, key, shift, ctrl, alt, event);
if (shift || alt || (ctrl && (key.includes('down') || key.includes('down')))) {
return;
}
if (this.grid.hasColumnGroups) {
this.handleMCHeaderNav(key, ctrl);
} else {
this.horizontalNav(event, key, -1, 'headerCell');
}
}
public focusTbody(event) {
const gridRows = this.grid.verticalScrollContainer.totalItemCount ?? this.grid.dataView.length;
if (gridRows < 1) {
this.activeNode = null;
return;
}
if (!this.activeNode || !Object.keys(this.activeNode).length || this.activeNode.row < 0 || this.activeNode.row > gridRows - 1) {
const hasLastActiveNode = Object.keys(this.lastActiveNode).length;
const shouldClearSelection = hasLastActiveNode && (this.lastActiveNode.row < 0 || this.lastActiveNode.row > gridRows - 1);
this.setActiveNode(this.lastActiveNode.row >= 0 && this.lastActiveNode.row < gridRows ?
this.firstVisibleNode(this.lastActiveNode.row) : this.firstVisibleNode());
if (shouldClearSelection || (this.grid.cellSelection !== GridSelectionMode.multiple)) {
this.grid.clearCellSelection();
this.grid.navigateTo(this.activeNode.row, this.activeNode.column, (obj) => {
obj.target?.activate(event);
this.grid.cdr.detectChanges();
});
} else {
if (hasLastActiveNode && !this.grid.selectionService.selected(this.lastActiveNode)) {
return;
}
const range = {
rowStart: this.activeNode.row, rowEnd: this.activeNode.row,
columnStart: this.activeNode.column, columnEnd: this.activeNode.column
};
this.grid.selectRange(range);
this.grid.notifyChanges();
}
}
}
public focusFirstCell(header = true) {
if ((header || this.grid.dataView.length) && this.activeNode &&
(this.activeNode.row === -1 || this.activeNode.row === this.grid.dataView.length ||
(!header && !this.grid.hasSummarizedColumns))) {
return;
}
const shouldScrollIntoView = this.lastActiveNode && (header && this.lastActiveNode.row !== -1) ||
(!header && this.lastActiveNode.row !== this.grid.dataView.length);
this.setActiveNode(this.firstVisibleNode(header ? -1 : this.grid.dataView.length));
if (shouldScrollIntoView) {
this.performHorizontalScrollToCell(this.activeNode.column);
}
}
public isColumnFullyVisible(columnIndex: number) {
if (columnIndex < 0 || this.isColumnPinned(columnIndex, this.forOfDir())) {
return true;
}
const index = this.getColumnUnpinnedIndex(columnIndex);
const width = this.forOfDir().getColumnScrollLeft(index + 1) - this.forOfDir().getColumnScrollLeft(index);
if (this.displayContainerWidth < width && this.displayContainerScrollLeft === this.forOfDir().getColumnScrollLeft(index)) {
return true;
}
return this.displayContainerWidth >= this.forOfDir().getColumnScrollLeft(index + 1) - this.displayContainerScrollLeft &&
this.displayContainerScrollLeft <= this.forOfDir().getColumnScrollLeft(index);
}
public shouldPerformHorizontalScroll(visibleColIndex: number, rowIndex = -1) {
if (visibleColIndex < 0 || visibleColIndex > this.grid.visibleColumns.length - 1) {
return false;
}
if (rowIndex < 0 || rowIndex > this.grid.dataView.length - 1) {
return !this.isColumnFullyVisible(visibleColIndex);
}
const row = this.grid.dataView[rowIndex];
return row.expression || row.detailsData ? false : !this.isColumnFullyVisible(visibleColIndex);
}
public shouldPerformVerticalScroll(targetRowIndex: number, _visibleColIndex: number): boolean {
if (this.grid.isRecordPinnedByViewIndex(targetRowIndex)) {
return false;
}
const scrollRowIndex = this.grid.hasPinnedRecords && this.grid.isRowPinningToTop ?
targetRowIndex - this.grid.pinnedDataView.length : targetRowIndex;
const targetRow = this.getRowElementByIndex(targetRowIndex);
const rowHeight = this.grid.verticalScrollContainer.getSizeAt(scrollRowIndex);
const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0;
const endTopOffset = targetRow ? targetRow.offsetTop + rowHeight + this.containerTopOffset : containerHeight + rowHeight;
// this is workaround: endTopOffset - containerHeight > 5 and should be replaced with: containerHeight < endTopOffset
// when the page is zoomed the grid does not scroll the row completely in the view
return !targetRow || targetRow.offsetTop < Math.abs(this.containerTopOffset)
|| containerHeight && endTopOffset - containerHeight > 5;
}
public performVerticalScrollToCell(rowIndex: number, visibleColIndex = -1, cb?: () => void) {
if (!this.shouldPerformVerticalScroll(rowIndex, visibleColIndex)) {
return;
}
this.pendingNavigation = true;
// Only for top pinning we need to subtract pinned count because virtualization indexing doesn't count pinned rows.
const scrollRowIndex = this.grid.hasPinnedRecords && this.grid.isRowPinningToTop ?
rowIndex - this.grid.pinnedDataView.length : rowIndex;
this.grid.verticalScrollContainer.scrollTo(scrollRowIndex);
this.grid.verticalScrollContainer.chunkLoad
.pipe(first()).subscribe(() => {
this.pendingNavigation = false;
if (cb) {
cb();
}
});
}
public performHorizontalScrollToCell(visibleColumnIndex: number, cb?: () => void) {
if (this.grid.rowList < 1 && this.grid.summariesRowList.length < 1 && this.grid.hasColumnGroups) {
let column = this.grid.getColumnByVisibleIndex(visibleColumnIndex);
while (column.parent) {
column = column.parent;
}
visibleColumnIndex = this.forOfDir().igxForOf.indexOf(column);
}
if (!this.shouldPerformHorizontalScroll(visibleColumnIndex)) {
return;
}
this.pendingNavigation = true;
this.grid.parentVirtDir.chunkLoad
.pipe(first())
.subscribe(() => {
this.pendingNavigation = false;
if (cb) {
cb();
}
});
this.forOfDir().scrollTo(this.getColumnUnpinnedIndex(visibleColumnIndex));
}
public isDataRow(rowIndex: number, includeSummary = false) {
let curRow: any;
if (rowIndex < 0 || rowIndex > this.grid.dataView.length - 1) {
curRow = this.grid.dataView[rowIndex - this.grid.virtualizationState.startIndex];
if (!curRow) {
// if data is remote, record might not be in the view yet.
return this.grid.verticalScrollContainer.isRemote && rowIndex >= 0 && rowIndex <= (this.grid as any).totalItemCount - 1;
}
} else {
curRow = this.grid.dataView[rowIndex];
}
return curRow && !this.grid.isGroupByRecord(curRow) && !this.grid.isDetailRecord(curRow)
&& !curRow.childGridsData && (includeSummary || !curRow.summaries);
}
public isGroupRow(rowIndex: number): boolean {
if (rowIndex < 0 || rowIndex > this.grid.dataView.length - 1) {
return false;
}
const curRow = this.grid.dataView[rowIndex];
return curRow && this.grid.isGroupByRecord(curRow);
}
public setActiveNode(activeNode: IActiveNode) {
if (!this.isActiveNodeChanged(activeNode)) {
return;
}
if (!this.activeNode) {
this.activeNode = activeNode;
}
Object.assign(this.activeNode, activeNode);
const currRow = this.grid.dataView[activeNode.row];
const type: GridKeydownTargetType = activeNode.row < 0 ? 'headerCell' :
this.isDataRow(activeNode.row) ? 'dataCell' :
currRow && this.grid.isGroupByRecord(currRow) ? 'groupRow' :
currRow && this.grid.isDetailRecord(currRow) ? 'masterDetailRow' : 'summaryCell';
const args: IActiveNodeChangeEventArgs = {
row: this.activeNode.row,
column: this.activeNode.column,
level: this.activeNode.level,
tag: type
};
this.grid.activeNodeChange.emit(args);
}
public isActiveNodeChanged(activeNode: IActiveNode) {
let isChanged = false;
const checkInnerProp = (aciveNode: ColumnGroupsCache | IMultiRowLayoutNode, prop) => {
if (!aciveNode) {
isChanged = true;
return;
}
props = Object.getOwnPropertyNames(aciveNode);
for (const propName of props) {
if (this.activeNode[prop][propName] !== aciveNode[propName]) {
isChanged = true;
}
}
};
if (!this.activeNode) {
return isChanged = true;
}
let props = Object.getOwnPropertyNames(activeNode);
for (const propName of props) {
if (!!this.activeNode[propName] && typeof this.activeNode[propName] === 'object') {
checkInnerProp(activeNode[propName], propName);
} else if (this.activeNode[propName] !== activeNode[propName]) {
isChanged = true;
}
}
return isChanged;
}
protected getNextPosition(rowIndex: number, colIndex: number, key: string, shift: boolean, ctrl: boolean, event: KeyboardEvent) {
if (!this.isDataRow(rowIndex, true) && (key.indexOf('down') < 0 || key.indexOf('up') < 0) && ctrl) {
return { rowIndex, colIndex };
}
switch (key) {
case 'pagedown':
case 'pageup':
event.preventDefault();
if (key === 'pagedown') {
this.grid.verticalScrollContainer.scrollNextPage();
} else {
this.grid.verticalScrollContainer.scrollPrevPage();
}
const editCell = this.grid.crudService.cell;
this.grid.verticalScrollContainer.chunkLoad
.pipe(first()).subscribe(() => {
if (editCell && this.grid.rowList.map(r => r.index).indexOf(editCell.rowIndex) < 0) {
this.grid.tbody.nativeElement.focus({ preventScroll: true });
}
});
break;
case 'tab':
this.handleEditing(shift, event);
break;
case 'end':
rowIndex = ctrl ? this.findLastDataRowIndex() : this.activeNode.row;
colIndex = this.lastColumnIndex;
break;
case 'home':
rowIndex = ctrl ? this.findFirstDataRowIndex() : this.activeNode.row;
colIndex = 0;
break;
case 'arrowleft':
case 'left':
colIndex = ctrl ? 0 : this.activeNode.column - 1;
break;
case 'arrowright':
case 'right':
colIndex = ctrl ? this.lastColumnIndex : this.activeNode.column + 1;
break;
case 'arrowup':
case 'up':
if (ctrl && !this.isDataRow(rowIndex) || (this.grid.rowEditable && this.grid.crudService.rowEditingBlocked)) {
break;
}
colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0;
rowIndex = ctrl ? this.findFirstDataRowIndex() : this.activeNode.row - 1;
break;
case 'arrowdown':
case 'down':
if ((ctrl && !this.isDataRow(rowIndex)) || (this.grid.rowEditable && this.grid.crudService.rowEditingBlocked)) {
break;
}
colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0;
rowIndex = ctrl ? this.findLastDataRowIndex() : this.activeNode.row + 1;
break;
case 'enter':
case 'f2':
const cell = this.grid.gridAPI.get_cell_by_visible_index(this.activeNode.row, this.activeNode.column);
if (!this.isDataRow(rowIndex) || !cell.editable) {
break;
}
this.grid.crudService.enterEditMode(cell, event);
break;
case 'escape':
case 'esc':
if (!this.isDataRow(rowIndex)) {
break;
}
if (this.grid.crudService.isInCompositionMode) {
return;
}
if (this.grid.crudService.cellInEditMode || this.grid.crudService.rowInEditMode) {
this.grid.crudService.endEdit(false, event);
if (this.platform.isEdge) {
this.grid.cdr.detectChanges();
}
this.grid.tbody.nativeElement.focus();
}
break;
case ' ':
case 'spacebar':
case 'space':
const rowObj = this.grid.gridAPI.get_row_by_index(this.activeNode.row);
if (this.grid.isRowSelectable && rowObj) {
if (this.isDataRow(rowIndex)) {
if (rowObj.selected) {
this.grid.selectionService.deselectRow(rowObj.key, event);
} else {
this.grid.selectionService.selectRowById(rowObj.key, false, event);
}
}
if (this.isGroupRow(rowIndex)) {
((rowObj as any) as IgxGridGroupByRowComponent).onGroupSelectorClick(event);
}
}
break;
default:
return;
}
return { rowIndex, colIndex };
}
protected horizontalNav(event: KeyboardEvent, key: string, rowIndex: number, tag: GridKeydownTargetType) {
const ctrl = event.ctrlKey;
if (!HORIZONTAL_NAV_KEYS.has(event.key.toLowerCase())) {
return;
}
event.preventDefault();
this.activeNode.row = rowIndex;
if (rowIndex > 0) {
if (this.emitKeyDown('summaryCell', this.activeNode.row, event)) {
return;
}
}
const newActiveNode = {
column: this.activeNode.column,
mchCache: {
level: this.activeNode.level,
visibleIndex: this.activeNode.column
}
};
if ((key.includes('left') || key === 'home') && this.activeNode.column > 0) {
newActiveNode.column = ctrl || key === 'home' ? 0 : this.activeNode.column - 1;
}
if ((key.includes('right') || key === 'end') && this.activeNode.column < this.lastColumnIndex) {
newActiveNode.column = ctrl || key === 'end' ? this.lastColumnIndex : this.activeNode.column + 1;
}
if (tag === 'headerCell') {
const column = this.grid.getColumnByVisibleIndex(newActiveNode.column);
newActiveNode.mchCache.level = column.level;
newActiveNode.mchCache.visibleIndex = column.visibleIndex;
}
this.setActiveNode({ row: this.activeNode.row, column: newActiveNode.column, mchCache: newActiveNode.mchCache });
this.performHorizontalScrollToCell(this.activeNode.column);
}
public get lastColumnIndex() {
return Math.max(...this.grid.visibleColumns.map(col => col.visibleIndex));
}
public get displayContainerWidth() {
return Math.round(this.grid.parentVirtDir.dc.instance._viewContainer.element.nativeElement.offsetWidth);
}
public get displayContainerScrollLeft() {
return Math.ceil(this.grid.headerContainer.scrollPosition);
}
public get containerTopOffset() {
return parseInt(this.grid.verticalScrollContainer.dc.instance._viewContainer.element.nativeElement.style.top, 10);
}
protected getColumnUnpinnedIndex(visibleColumnIndex: number) {
const column = this.grid.unpinnedColumns.find((col) => !col.columnGroup && col.visibleIndex === visibleColumnIndex);
return this.grid.pinnedColumns.length ? this.grid.unpinnedColumns.filter((c) => !c.columnGroup).indexOf(column) :
visibleColumnIndex;
}
protected forOfDir(): IgxForOfDirective<any> {
const forOfDir = this.grid.dataRowList.length > 0 ? this.grid.dataRowList.first.virtDirRow : this.grid.summariesRowList.length ?
this.grid.summariesRowList.first.virtDirRow : this.grid.headerContainer;
return forOfDir as IgxForOfDirective<any>;
}
protected handleAlt(key: string, event: KeyboardEvent) {
event.preventDefault();
// todo TODO ROW
const row = this.grid.gridAPI.get_row_by_index(this.activeNode.row);
if (!(this.isToggleKey(key) || this.isAddKey(key)) || !row) {
return;
}
if (this.isAddKey(key)) {
if (!this.grid.rowEditable) {
console.warn('The grid must be in row edit mode to perform row adding!');
return;
}
if (event.shiftKey && row.treeRow !== undefined) {
this.grid.crudService.enterAddRowMode(row, true, event);
} else if (!event.shiftKey) {
this.grid.crudService.enterAddRowMode(row, false, event);
}
} else if (!row.expanded && ROW_EXPAND_KEYS.has(key)) {
if (row.key === undefined) {
// TODO use expanded row.expanded = !row.expanded;
(row as any).toggle();
} else {
this.grid.gridAPI.set_row_expansion_state(row.key, true, event);
}
} else if (row.expanded && ROW_COLLAPSE_KEYS.has(key)) {
if (row.key === undefined) {
// TODO use expanded row.expanded = !row.expanded;
(row as any).toggle();
} else {
this.grid.gridAPI.set_row_expansion_state(row.key, false, event);
}
}
this.grid.notifyChanges();
}
protected handleEditing(shift: boolean, event: KeyboardEvent) {
const next = shift ? this.grid.getPreviousCell(this.activeNode.row, this.activeNode.column, col => col.editable) :
this.grid.getNextCell(this.activeNode.row, this.activeNode.column, col => col.editable);
if (!this.grid.crudService.rowInEditMode && this.isActiveNode(next.rowIndex, next.visibleColumnIndex)) {
this.grid.crudService.endEdit(true, event);
this.grid.tbody.nativeElement.focus();
return;
}
event.preventDefault();
if ((this.grid.crudService.rowInEditMode && this.grid.rowEditTabs.length) &&
(this.activeNode.row !== next.rowIndex || this.isActiveNode(next.rowIndex, next.visibleColumnIndex))) {
const args = this.grid.crudService.updateCell(true, event);
if (args.cancel) {
return;
} else if (shift) {
this.grid.rowEditTabs.last.element.nativeElement.focus();
} else {
this.grid.rowEditTabs.first.element.nativeElement.focus();
}
return;
}
if (this.grid.crudService.rowInEditMode && !this.grid.rowEditTabs.length) {
if (shift && next.rowIndex === this.activeNode.row && next.visibleColumnIndex === this.activeNode.column) {
next.visibleColumnIndex = this.grid.lastEditableColumnIndex;
} else if (!shift && next.rowIndex === this.activeNode.row && next.visibleColumnIndex === this.activeNode.column) {
next.visibleColumnIndex = this.grid.firstEditableColumnIndex;
} else {
next.rowIndex = this.activeNode.row;
}
}
this.navigateInBody(next.rowIndex, next.visibleColumnIndex, (obj) => {
obj.target.activate(event);
this.grid.cdr.detectChanges();
});
}
protected navigateInBody(rowIndex, visibleColIndex, cb: (arg: any) => void = null): void {
if (!this.isValidPosition(rowIndex, visibleColIndex) || this.isActiveNode(rowIndex, visibleColIndex)) {
return;
}
this.grid.navigateTo(rowIndex, visibleColIndex, cb);
}
protected emitKeyDown(type: GridKeydownTargetType, rowIndex, event) {
const row = this.grid.summariesRowList.toArray().concat(this.grid.rowList.toArray()).find(r => r.index === rowIndex);
if (!row) {
return;
}
const target = type === 'groupRow' ? row :
type === 'dataCell' ? row.cells?.find(c => c.visibleColumnIndex === this.activeNode.column) :
row.summaryCells?.find(c => c.visibleColumnIndex === this.activeNode.column);
const keydownArgs = { targetType: type, event, cancel: false, target };
this.grid.gridKeydown.emit(keydownArgs);
if (keydownArgs.cancel && type === 'dataCell') {
this.grid.selectionService.clear();
this.grid.selectionService.keyboardState.active = true;
return keydownArgs.cancel;
}
}
protected isColumnPinned(columnIndex: number, forOfDir: IgxForOfDirective<any>): boolean {
const horizontalScroll = forOfDir.getScroll();
return (!horizontalScroll.clientWidth || this.grid.getColumnByVisibleIndex(columnIndex)?.pinned);
}
protected findFirstDataRowIndex(): number {
return this.grid.dataView.findIndex(rec => !this.grid.isGroupByRecord(rec) && !this.grid.isDetailRecord(rec) && !rec.summaries);
}
protected findLastDataRowIndex(): number {
if ((this.grid as any).totalItemCount) {
return (this.grid as any).totalItemCount - 1;
}
let i = this.grid.dataView.length;
while (i--) {
if (this.isDataRow(i)) {
return i;
}
}
}
protected getRowElementByIndex(index) {
if (this.grid.hasDetails) {
const detail = this.grid.nativeElement.querySelector(`[detail="true"][data-rowindex="${index}"]`);
if (detail) {
return detail;
}
}
return this.grid.rowList.toArray().concat(this.grid.summariesRowList.toArray()).find(r => r.index === index)?.nativeElement;
}
protected isValidPosition(rowIndex: number, colIndex: number): boolean {
const length = (this.grid as any).totalItemCount ?? this.grid.dataView.length;
if (rowIndex < 0 || colIndex < 0 || length - 1 < rowIndex || this.lastColumnIndex < colIndex) {
return false;
}
return this.activeNode.column !== colIndex && !this.isDataRow(rowIndex, true) ? false : true;
}
protected performHeaderKeyCombination(column, key, shift, ctrl, alt, event) {
let direction = this.grid.sortingExpressions.find(expr => expr.fieldName === column.field)?.dir;
if (ctrl && key.includes('up') && column.sortable && !column.columnGroup) {
direction = direction === SortingDirection.Asc ? SortingDirection.None : SortingDirection.Asc;
this.grid.sort({ fieldName: column.field, dir: direction, ignoreCase: false });
return;
}
if (ctrl && key.includes('down') && column.sortable && !column.columnGroup) {
direction = direction === SortingDirection.Desc ? SortingDirection.None : SortingDirection.Desc;
this.grid.sort({ fieldName: column.field, dir: direction, ignoreCase: false });
return;
}
if (shift && alt && this.isToggleKey(key) && !column.columnGroup && column.groupable) {
direction = direction || SortingDirection.Asc;
if (key.includes('right')) {
(this.grid as any).groupBy({
fieldName: column.field,
dir: direction,
ignoreCase: column.sortingIgnoreCase,
strategy: column.sortStrategy,
groupingComparer: column.groupingComparer,
});
} else {
(this.grid as any).clearGrouping(column.field);
}
this.activeNode.column = key.includes('right') && (this.grid as any).hideGroupedColumns &&
column.visibleIndex === this.lastColumnIndex ? this.lastColumnIndex - 1 : this.activeNode.column;
return;
}
if (alt && (ROW_EXPAND_KEYS.has(key) || ROW_COLLAPSE_KEYS.has(key))) {
this.handleMCHExpandCollapse(key, column);
return;
}
if ([' ', 'spacebar', 'space'].indexOf(key) !== -1) {
this.handleColumnSelection(column, event);
}
if (alt && (key === 'l' || key === '¬') && this.grid.allowAdvancedFiltering) {
this.grid.openAdvancedFilteringDialog();
}
if (ctrl && shift && key === 'l' && this.grid.allowFiltering && !column.columnGroup && column.filterable) {
if (this.grid.filterMode === FilterMode.excelStyleFilter) {
const headerEl = this.grid.headerGroups.find(g => g.active).nativeElement;
this.grid.filteringService.toggleFilterDropdown(headerEl, column);
} else {
this.performHorizontalScrollToCell(column.visibleIndex);
this.grid.filteringService.filteredColumn = column;
this.grid.filteringService.isFilterRowVisible = true;
}
}
}
private firstVisibleNode(rowIndex?) {
const colIndex = this.lastActiveNode.column !== undefined ? this.lastActiveNode.column :
this.grid.visibleColumns.sort((c1, c2) => c1.visibleIndex - c2.visibleIndex)
.find(c => this.isColumnFullyVisible(c.visibleIndex))?.visibleIndex;
const column = this.grid.visibleColumns.find((col) => !col.columnLayout && col.visibleIndex === colIndex);
const rowInd = rowIndex ? rowIndex : this.grid.rowList.find(r => !this.shouldPerformVerticalScroll(r.index, colIndex))?.index;
const node = {
row: rowInd ?? 0,
column: column?.visibleIndex ?? 0, level: column?.level ?? 0,
mchCache: column ? { level: column.level, visibleIndex: column.visibleIndex } : {} as ColumnGroupsCache,
layout: column && column.columnLayoutChild ? {
rowStart: column.rowStart, colStart: column.colStart,
rowEnd: column.rowEnd, colEnd: column.colEnd, columnVisibleIndex: column.visibleIndex
} : null
};
return node;
}
private handleMCHeaderNav(key: string, ctrl: boolean) {
const newHeaderNode: ColumnGroupsCache = {
visibleIndex: this.activeNode.mchCache.visibleIndex,
level: this.activeNode.mchCache.level
};
const activeCol = this.currentActiveColumn;
const lastGroupIndex = Math.max(... this.grid.visibleColumns.
filter(c => c.level <= this.activeNode.level).map(col => col.visibleIndex));
let nextCol = activeCol;
if ((key.includes('left') || key === 'home') && this.activeNode.column > 0) {
const index = ctrl || key === 'home' ? 0 : this.activeNode.column - 1;
nextCol = this.getNextColumnMCH(index);
newHeaderNode.visibleIndex = nextCol.visibleIndex;
}
if ((key.includes('right') || key === 'end') && activeCol.visibleIndex < lastGroupIndex) {
const nextVIndex = activeCol.children ? Math.max(...activeCol.allChildren.map(c => c.visibleIndex)) + 1 :
activeCol.visibleIndex + 1;
nextCol = ctrl || key === 'end' ? this.getNextColumnMCH(this.lastColumnIndex) : this.getNextColumnMCH(nextVIndex);
newHeaderNode.visibleIndex = nextCol.visibleIndex;
}
if (!ctrl && key.includes('up') && this.activeNode.level > 0) {
nextCol = activeCol.parent;
newHeaderNode.level = nextCol.level;
}
if (!ctrl && key.includes('down') && activeCol.children) {
nextCol = activeCol.children.find(c => c.visibleIndex === newHeaderNode.visibleIndex) ||
activeCol.children.toArray().sort((a, b) => b.visibleIndex - a.visibleIndex)
.filter(col => col.visibleIndex < newHeaderNode.visibleIndex)[0];
newHeaderNode.level = nextCol.level;
}
this.setActiveNode({
row: this.activeNode.row,
column: nextCol.visibleIndex,
level: nextCol.level,
mchCache: newHeaderNode
});
this.performHorizontalScrollToCell(nextCol.visibleIndex);
}
private handleMCHExpandCollapse(key, column) {
if (!column.children || !column.collapsible) {
return;
}
if (!column.expanded && ROW_EXPAND_KEYS.has(key)) {
column.expanded = true;
} else if (column.expanded && ROW_COLLAPSE_KEYS.has(key)) {
column.expanded = false;
}
}
private handleColumnSelection(column, event) {
if (!column.selectable || this.grid.columnSelection === GridSelectionMode.none) {
return;
}
const clearSelection = this.grid.columnSelection === GridSelectionMode.single;
const columnsToSelect = !column.children ? [column.field] :
column.allChildren.filter(c => !c.hidden && c.selectable && !c.columnGroup).map(c => c.field);
if (column.selected) {
this.grid.selectionService.deselectColumns(columnsToSelect, event);
} else {
this.grid.selectionService.selectColumns(columnsToSelect, clearSelection, false, event);
}
}
private getNextColumnMCH(visibleIndex) {
let col = this.grid.getColumnByVisibleIndex(visibleIndex);
let parent = col.parent;
while (parent && col.level > this.activeNode.mchCache.level) {
col = col.parent;
parent = col.parent;
}
return col;
}
private get currentActiveColumn() {
return this.grid.visibleColumns.find(c => c.visibleIndex === this.activeNode.column && c.level === this.activeNode.level);
}
private isActiveNode(rIndex: number, cIndex: number): boolean {
return this.activeNode ? this.activeNode.row === rIndex && this.activeNode.column === cIndex : false;
}
private isToggleKey(key: string): boolean {
return ROW_COLLAPSE_KEYS.has(key) || ROW_EXPAND_KEYS.has(key);
}
private isAddKey(key: string): boolean {
return ROW_ADD_KEYS.has(key);
}
}