Skip to content

Commit

Permalink
Merge pull request #4267 from IgniteUI/mkirova/fix-4217
Browse files Browse the repository at this point in the history
Refactoring filter cell navigation so that it is handled in the navigation service. Handling special scenarios for hierarchical grid in the hierarchical navigation service.
  • Loading branch information
mpavlinov committed Apr 1, 2019
2 parents 9067262 + 22d1407 commit f171ea7
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,46 +82,17 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit, DoC

@HostListener('keydown.tab', ['$event'])
public onTabKeyDown(eventArgs) {
const nextIndex = this.filteringService.unpinnedFilterableColumns.indexOf(this.column) + 1;

if (this.isLastElementFocused()) {
if (this.column === this.getLastPinnedFilterableColumn() &&
(!this.isColumnLeftVisible(nextIndex) || !this.isColumnRightVisible(nextIndex))) {
this.filteringService.scrollToFilterCell(this.filteringService.unpinnedFilterableColumns[nextIndex], false);
eventArgs.stopPropagation();
return;
}

if (nextIndex >= this.filteringService.unpinnedFilterableColumns.length) {
if (!this.filteringService.grid.filteredData || this.filteringService.grid.filteredData.length > 0) {
if (this.filteringService.grid.rowList.filter(row => row instanceof IgxGridGroupByRowComponent).length > 0) {
eventArgs.stopPropagation();
return;
}
this.navService.goToFirstCell();
}
eventArgs.preventDefault();
} else if (!this.column.pinned && !this.isColumnRightVisible(nextIndex)) {
eventArgs.preventDefault();
this.filteringService.scrollToFilterCell(this.filteringService.unpinnedFilterableColumns[nextIndex], true);
}
this.filteringService.grid.navigation.navigateNextFilterCell(this.column, eventArgs);
}
eventArgs.stopPropagation();
}

@HostListener('keydown.shift.tab', ['$event'])
public onShiftTabKeyDown(eventArgs) {
if (this.isFirstElementFocused()) {
const prevIndex = this.filteringService.unpinnedFilterableColumns.indexOf(this.column) - 1;

if (prevIndex >= 0 && this.column.visibleIndex > 0 && !this.isColumnLeftVisible(prevIndex) && !this.column.pinned) {
eventArgs.preventDefault();
this.filteringService.scrollToFilterCell(this.filteringService.unpinnedFilterableColumns[prevIndex], false);
} else if (this.column.visibleIndex === 0 ||
(prevIndex < 0 && !this.getFirstPinnedFilterableColumn()) ||
this.column === this.getFirstPinnedFilterableColumn()) {
eventArgs.preventDefault();
}
this.filteringService.grid.navigation.navigatePrevFilterCell(this.column, eventArgs);
}
eventArgs.stopPropagation();
}
Expand Down Expand Up @@ -360,16 +331,6 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit, DoC
}
}

private getLastPinnedFilterableColumn(): IgxColumnComponent {
const pinnedFilterableColums =
this.filteringService.grid.pinnedColumns.filter(col => !(col instanceof IgxColumnGroupComponent) && col.filterable);
return pinnedFilterableColums[pinnedFilterableColums.length - 1];
}

private getFirstPinnedFilterableColumn(): IgxColumnComponent {
return this.filteringService.grid.pinnedColumns.filter(col => !(col instanceof IgxColumnGroupComponent) && col.filterable)[0];
}

private isColumnRightVisible(columnIndex: number): boolean {
if (this.filteringService.areAllColumnsInView) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2460,7 +2460,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
protected resolver: ComponentFactoryResolver,
protected differs: IterableDiffers,
protected viewRef: ViewContainerRef,
private navigation: IgxGridNavigationService,
public navigation: IgxGridNavigationService,
public filteringService: IgxFilteringService,
@Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
public summaryService: IgxGridSummaryService,
Expand Down
69 changes: 65 additions & 4 deletions projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { IgxGridBaseComponent, FilterMode } from './grid-base.component';
import { first } from 'rxjs/operators';
import { IgxColumnComponent } from './column.component';
import { IgxGridGroupByRowComponent } from './grid/groupby-row.component';

enum MoveDirection {
LEFT = 'left',
Expand Down Expand Up @@ -461,15 +462,75 @@ export class IgxGridNavigationService {
}
}

public moveFocusToFilterCell() {
public moveFocusToFilterCell(toStart?: boolean) {
const columns = this.grid.filteringService.unpinnedFilterableColumns;
if (this.isColumnFullyVisible(columns.length - 1)) {
this.grid.filteringService.focusFilterCellChip(columns[columns.length - 1], false);
const targetIndex = toStart ? 0 : columns.length - 1;
const visibleIndex = columns[targetIndex].visibleIndex;
const isVisible = toStart ? this.isColumnLeftFullyVisible(visibleIndex) : this.isColumnFullyVisible(visibleIndex);
if (isVisible) {
this.grid.filteringService.focusFilterCellChip(columns[targetIndex], false);
} else {
this.grid.filteringService.scrollToFilterCell(columns[columns.length - 1], false);
this.grid.filteringService.scrollToFilterCell(columns[targetIndex], false);
}
}

public navigatePrevFilterCell(column: IgxColumnComponent, eventArgs) {
const cols = this.grid.filteringService.unpinnedFilterableColumns;
const prevFilterableIndex = cols.indexOf(column) - 1;
const visibleIndex = column.visibleIndex;
if (visibleIndex === 0 || prevFilterableIndex < 0) {
// prev is not filter cell
const firstFiltarableCol = this.getFirstPinnedFilterableColumn();
if (!firstFiltarableCol || column === firstFiltarableCol) {
eventArgs.preventDefault();
}
return;
}
const prevColumn = cols[prevFilterableIndex];
const prevVisibleIndex = prevColumn.visibleIndex;

if (prevFilterableIndex >= 0 && visibleIndex > 0 && !this.isColumnLeftFullyVisible(prevVisibleIndex) && !column.pinned) {
eventArgs.preventDefault();
this.grid.filteringService.scrollToFilterCell(prevColumn, false);
}
}

public navigateNextFilterCell(column: IgxColumnComponent, eventArgs) {
const cols = this.grid.filteringService.unpinnedFilterableColumns;
const nextFilterableIndex = cols.indexOf(column) + 1;
if (nextFilterableIndex >= this.grid.filteringService.unpinnedFilterableColumns.length) {
// next is not filter cell
if (!this.grid.filteringService.grid.filteredData || this.grid.filteringService.grid.filteredData.length > 0) {
if (this.grid.filteringService.grid.rowList.filter(row => row instanceof IgxGridGroupByRowComponent).length > 0) {
eventArgs.stopPropagation();
return;
}
this.goToFirstCell();
}
eventArgs.preventDefault();
return;
}
const nextColumn = cols[nextFilterableIndex];
const nextVisibleIndex = nextColumn.visibleIndex;
if (!column.pinned && !this.isColumnFullyVisible(nextVisibleIndex)) {
eventArgs.preventDefault();
this.grid.filteringService.scrollToFilterCell(nextColumn, true);
} else if (column === this.getLastPinnedFilterableColumn() && !this.isColumnFullyVisible(nextVisibleIndex)) {
this.grid.filteringService.scrollToFilterCell(nextColumn, false);
eventArgs.stopPropagation();
}
}

private getLastPinnedFilterableColumn(): IgxColumnComponent {
const pinnedFilterableColums =
this.grid.pinnedColumns.filter(col => !(col.columnGroup) && col.filterable);
return pinnedFilterableColums[pinnedFilterableColums.length - 1];
}

private getFirstPinnedFilterableColumn(): IgxColumnComponent {
return this.grid.pinnedColumns.filter(col => !(col.columnGroup) && col.filterable)[0];
}

public performShiftTabKey(currentRowEl, rowIndex, visibleColumnIndex, isSummary = false) {
if (isSummary && rowIndex === 0 && visibleColumnIndex === 0 && this.grid.rowList.length) {
this.goToLastBodyElement();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IgxGridNavigationService } from '../grid-navigation.service';
import { IgxHierarchicalGridComponent } from './hierarchical-grid.component';
import { first } from 'rxjs/operators';
import { FilterMode } from '../grid-base.component';
import { IgxColumnComponent } from '../../grids/column.component';

export class IgxHierarchicalGridNavigationService extends IgxGridNavigationService {
public grid: IgxHierarchicalGridComponent;
Expand Down Expand Up @@ -249,9 +251,28 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
const nextIsDataRow = this.grid.dataRowList.find(row => row.index === rowIndex + 1) ;
const isLastColumn = this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex === visibleColumnIndex;
const isLastSummaryRow = hasSummaries && isSummaryRow;
const nextIndex = rowIndex + 1;
const virt = this.grid.verticalScrollContainer;
const isNextChild = nextIndex <= virt.igxForOf.length - 1 &&
this.grid.isChildGridRecord(virt.igxForOf[nextIndex]);
if (!nextIsDataRow && !(isLastDataRow && hasSummaries) && isLastColumn && !isSummaryRow) {
// navigating in child, next is not summary
this.navigateDown(currentRowEl, rowIndex, 0);
const childContainer = this.getChildGridRowContainer();
const nextIsSiblingChild = this.grid.parent ? !!childContainer.nextElementSibling : false;
if (nextIsSiblingChild) {
this.focusNextChildDOMElem(childContainer, this.grid.parent);
} else if (isNextChild) {
const isInView = virt.state.startIndex + virt.state.chunkSize > nextIndex;
if (!isInView) {
this.scrollGrid(this.grid, 'next', () => {
this.focusNextChildDOMElem(currentRowEl, this.grid);
});
} else {
this.focusNextChildDOMElem(currentRowEl, this.grid);
}
} else {
this.navigateDown(currentRowEl, rowIndex, 0);
}
} else if (isLastSummaryRow && isLastColumn && this.grid.parent) {
// navigating in child summary, next is parent summary or next parent row
const parent = this.grid.parent;
Expand All @@ -274,13 +295,61 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
// navigating in child rows, next is child grid's summary row
this.focusNextRow(summaryRows[0].nativeElement, 0, this.grid.parent, true);
} else {
// navigating in normal cells
super.performTab(currentRowEl, rowIndex, visibleColumnIndex, isSummaryRow);
}
}

private focusNextChildDOMElem(currentRowEl, grid) {
const gridElem = currentRowEl.nextElementSibling.querySelector('igx-hierarchical-grid');
const childGridID = gridElem.getAttribute('id');
const childGrid = this.getChildGrid(childGridID, grid);
if (childGrid.allowFiltering && childGrid.filterMode === FilterMode.quickFilter) {
childGrid.navigation.moveFocusToFilterCell(true);
return;
}
this.focusNextChild(currentRowEl.nextElementSibling, 0, grid);
}

public navigatePrevFilterCell(column: IgxColumnComponent, eventArgs) {
if (column.visibleIndex === 0 && this.grid.parent) {
eventArgs.preventDefault();
let targetGrid = this.grid.parent;
const prevSiblingChild = this.getChildGridRowContainer().previousElementSibling;
if (prevSiblingChild) {
const gridElem = prevSiblingChild.querySelectorAll('igx-hierarchical-grid')[0];
targetGrid = this.getChildGrid(gridElem.getAttribute('id'), this.grid.parent);
}
this.focusPrev(targetGrid.unpinnedColumns[targetGrid.unpinnedColumns.length - 1].visibleIndex);
} else {
super.navigatePrevFilterCell(column, eventArgs);
}
}

public navigateNextFilterCell(column: IgxColumnComponent, eventArgs) {
const cols = this.grid.filteringService.unpinnedFilterableColumns;
const nextFilterableIndex = cols.indexOf(column) + 1;
if (nextFilterableIndex >= this.grid.filteringService.unpinnedFilterableColumns.length) {
// next is not filter cell
const dataRows = this.grid.rowList.toArray();
const hasRows = dataRows.length !== 0;
const summaryRows = this.grid.summariesRowList.toArray();
const hasSummaries = summaryRows.length > 0 && summaryRows[0].summaryCells.length > 0;
if (hasRows) {
this.focusNextRow(dataRows[0].nativeElement, 0, this.grid, false);
} else if (hasSummaries) {
this.focusNextRow(summaryRows[0].nativeElement, 0, this.grid, true);
} else {
this.focusNext(0);
}
eventArgs.preventDefault();
} else {
super.navigateNextFilterCell(column, eventArgs);
}
}

public performShiftTabKey(currentRowEl, rowIndex, visibleColumnIndex, isSummary = false) {
if (visibleColumnIndex === 0 && rowIndex === 0 && this.grid.parent && !isSummary) {
if (this.grid.allowFiltering) {
if (this.grid.allowFiltering && this.grid.filterMode === FilterMode.quickFilter) {
this.moveFocusToFilterCell();
} else {
const prevSiblingChild = this.getChildGridRowContainer().previousElementSibling;
Expand All @@ -298,7 +367,15 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
this.performShiftTabIntoChild(gridElem, currentRowEl, rowIndex);
} else if (visibleColumnIndex === 0 && isSummary) {
const lastRowIndex = this.grid.verticalScrollContainer.igxForOf.length - 1;
if (!this.getIsChildAtIndex(lastRowIndex)) {
if (lastRowIndex === -1) {
// no child data
if (this.grid.allowFiltering && this.grid.filterMode === FilterMode.quickFilter) {
this.moveFocusToFilterCell();
} else {
this.navigateUp(currentRowEl, rowIndex,
this.grid.parent.unpinnedColumns[this.grid.parent.unpinnedColumns.length - 1].visibleIndex);
}
} else if (!this.getIsChildAtIndex(lastRowIndex)) {
super.goToLastCell();
} else {
const scrTopPosition = this.grid.verticalScrollContainer.getScrollForIndex(lastRowIndex, true);
Expand Down Expand Up @@ -330,13 +407,18 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
const childGrid = this.getChildGrid(childGridID, this.grid) || this.getChildGrid(childGridID, this.grid.parent);
const lastIndex = childGrid.unpinnedColumns[childGrid.unpinnedColumns.length - 1].visibleIndex;
const summaryRows = childGrid.summariesRowList.toArray();
if (summaryRows.length > 0) {
if (summaryRows.length > 0 && summaryRows[0].summaryCells.length > 0) {
// move focus to last summary row cell
const summaryRow = summaryRows[0].nativeElement;
this.focusPrevRow(summaryRow, lastIndex, childGrid, true, true);
return;
} else if (childGrid.rowList.toArray().length === 0 &&
childGrid.allowFiltering && childGrid.filterMode === FilterMode.quickFilter) {
// move to filter cell
childGrid.navigation.moveFocusToFilterCell();
} else {
// move to next cell
this.navigateUp(currentRowEl, rowIndex, lastIndex);
}
this.navigateUp(currentRowEl, rowIndex, lastIndex);
}

private _focusScrollCellInView(visibleColumnIndex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,36 @@ describe('IgxHierarchicalGrid Basic Navigation', () => {

}));

it('should move focus to/from filter chip when navigat with Tab/Shift+Tab from parent to child that has filtering. ', (async () => {
const child1 = hierarchicalGrid.hgridAPI.getChildGrids(false)[0];
child1.allowFiltering = true;
fixture.detectChanges();

const horizontalScrDir = hierarchicalGrid.dataRowList.toArray()[0].virtDirRow;
horizontalScrDir.scrollTo(6);
await wait(100);
fixture.detectChanges();

const lastParentCell = hierarchicalGrid.getCellByColumn(0, 'childData2');
lastParentCell.nativeElement.focus();
fixture.detectChanges();

lastParentCell.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
await wait(100);
fixture.detectChanges();

const filterItems = fixture.debugElement.queryAll(By.css('.igx-chip__item'));
const firstFilterItem = filterItems[0].nativeElement;
expect(document.activeElement === firstFilterItem).toBeTruthy();

firstFilterItem.closest('.igx-grid__filtering-cell').dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }));
await wait(100);
fixture.detectChanges();

expect(lastParentCell.selected).toBeTruthy();
expect(lastParentCell.focused).toBeTruthy();
}));

it('should navigate inside summary row with Ctrl + Arrow Right/ Ctrl + Arrow Left', (async () => {
const col = hierarchicalGrid.getColumnByName('ID');
col.hasSummary = true;
Expand Down

0 comments on commit f171ea7

Please sign in to comment.