Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve keyboard navigation in filtering #2964

Merged
merged 7 commits into from
Nov 9, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</igx-chip>
<span class="igx-filtering-chips__connector" *ngIf="!last">{{filteringService.getOperatorAsString(item.afterOperator)}}</span>
</ng-container>
<div #moreIcon [ngClass]="filteringIndicatorClass()" (click)="onChipClicked()" tabindex="0">
<div #moreIcon [ngClass]="filteringIndicatorClass()" (click)="onChipClicked()" (keydown)="onChipKeyDown($event)" tabindex="0">
<igx-icon>filter_list</igx-icon>
<igx-badge [value]="moreFiltersCount"></igx-badge>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,52 +96,40 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit {
}

ngOnInit(): void {
this.filteringService.columnToChipToFocus.set(this.column.field, false);
this.filteringService.columnToMoreIconHidden.set(this.column.field, true);
}

ngAfterViewInit(): void {
this.updateFilterCellArea();
}

@HostListener('keydown.shift.tab', ['$event'])
@HostListener('keydown.tab', ['$event'])
public onTabKeyDown(eventArgs) {
if (eventArgs.shiftKey) {
if (this.column.visibleIndex > 0 && !this.navService.isColumnLeftFullyVisible(this.column.visibleIndex - 1)) {
eventArgs.preventDefault();
this.filteringService.grid.headerContainer.scrollTo(this.column.visibleIndex - 1);
} else if (this.column.visibleIndex === 0) {
eventArgs.preventDefault();
}
} else {
if (this.isLastElementFocused()) {
if (this.column.visibleIndex === this.filteringService.grid.columnList.length - 1) {
if (this.currentTemplate === this.defaultFilter) {
if (this.isMoreIconVisible() === false) {
if (this.moreIcon.nativeElement === document.activeElement) {
this.navService.goToFirstCell();
}
} else if (this.chipsArea.chipsList.last.elementRef.nativeElement.querySelector(`.igx-chip__item`) ===
document.activeElement) {
this.navService.goToFirstCell();
}
} else {
eventArgs.preventDefault();
if (!this.filteringService.grid.filteredData || this.filteringService.grid.filteredData.length > 0) {
this.navService.goToFirstCell();
}
} else if (!this.navService.isColumnFullyVisible(this.column.visibleIndex + 1)) {
eventArgs.preventDefault();
this.filteringService.grid.headerContainer.scrollTo(this.column.visibleIndex + 1);
this.ScrollToChip(this.column.visibleIndex + 1, true);
}
}

eventArgs.stopPropagation();
}

/**
* Returns the chip to be focused.
*/
public getChipToFocus() {
return this.filteringService.columnToChipToFocus.get(this.column.field);
@HostListener('keydown.shift.tab', ['$event'])
public onShiftTabKeyDown(eventArgs) {
if (this.isFirstElementFocused()) {
if (this.column.visibleIndex > 0 && !this.navService.isColumnLeftFullyVisible(this.column.visibleIndex - 1)) {
eventArgs.preventDefault();
this.ScrollToChip(this.column.visibleIndex - 1, false);
} else if (this.column.visibleIndex === 0) {
eventArgs.preventDefault();
}
}
eventArgs.stopPropagation();
}

/**
Expand Down Expand Up @@ -199,6 +187,7 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit {
public onChipRemoved(eventArgs: IBaseChipEventArgs, item: ExpressionUI): void {
const indexToRemove = this.expressionsList.indexOf(item);
this.removeExpression(indexToRemove);
this.focusChip();
}

/**
Expand All @@ -224,20 +213,20 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit {
*/
public filteringIndicatorClass() {
return {
[this.baseClass]: !this.isMoreIconVisible(),
[`${this.baseClass}--hidden`]: this.isMoreIconVisible()
[this.baseClass]: !this.isMoreIconHidden(),
[`${this.baseClass}--hidden`]: this.isMoreIconHidden()
};
}

/**
* Focus a chip depending on the current visible template.
*/
public focusChip() {
public focusChip(focusFirst: boolean = false) {
if (this.currentTemplate === this.defaultFilter) {
if (this.isMoreIconVisible() === false) {
this.moreIcon.nativeElement.focus();
if (focusFirst) {
this.focusFirstElement();
} else {
this.chipsArea.chipsList.last.elementRef.nativeElement.querySelector(`.igx-chip__item`).focus();
this.focusElement();
}
} else if (this.currentTemplate === this.emptyFilter) {
this.ghostChip.elementRef.nativeElement.querySelector(`.igx-chip__item`).focus();
Expand All @@ -264,7 +253,7 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit {
this.filteringService.filter(this.column.field, this.rootExpressionsTree);
}

private isMoreIconVisible(): boolean {
private isMoreIconHidden(): boolean {
return this.filteringService.columnToMoreIconHidden.get(this.column.field);
}

Expand Down Expand Up @@ -309,4 +298,52 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit {
this.cdr.detectChanges();
}
}

private ScrollToChip(columnIndex: number, shouldFocusNext: boolean) {
this.filteringService.grid.nativeElement.focus({preventScroll: true});
this.filteringService.columnToFocus = this.filteringService.grid.visibleColumns[columnIndex];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting columnToFocus and focusNext happens only in filtering cell component and in navigation service. And in the both places these two fields are set together and are never read. So in order to make the code a little bit easier to use and error prone we could create a method in the filtering service to set those two fields. They are only used in the service so make them private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focusNext is read in the filtering cell when we decide in which direction we should move the focus.

this.filteringService.shouldFocusNext = shouldFocusNext;
this.filteringService.grid.headerContainer.scrollTo(columnIndex);
}

private isFirstElementFocused(): boolean {
return !(this.chipsArea && this.chipsArea.chipsList.length > 0 &&
this.chipsArea.chipsList.first.elementRef.nativeElement.querySelector(`.igx-chip__item`) !== document.activeElement);
}

private isLastElementFocused(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could simplify this method. Too many if/else clauses.

if (this.chipsArea) {
if (this.isMoreIconHidden() && this.chipsArea.chipsList.last.elementRef.nativeElement.querySelector(`.igx-chip__remove`) !==
document.activeElement) {
return false;
} else if (!this.isMoreIconHidden() && this.moreIcon.nativeElement !== document.activeElement) {
return false;
}
}
return true;
}

private focusFirstElement(): void {
if (this.chipsArea.chipsList.length > 0) {
this.chipsArea.chipsList.first.elementRef.nativeElement.querySelector(`.igx-chip__item`).focus();
} else {
this.moreIcon.nativeElement.focus();
}
}

private focusElement(): void {
if (this.filteringService.shouldFocusNext) {
if (!this.isMoreIconHidden() && this.chipsArea.chipsList.length === 0) {
this.moreIcon.nativeElement.focus();
} else {
this.chipsArea.chipsList.first.elementRef.nativeElement.querySelector(`.igx-chip__item`).focus();
}
} else {
if (!this.isMoreIconHidden()) {
this.moreIcon.nativeElement.focus();
} else {
this.chipsArea.chipsList.last.elementRef.nativeElement.querySelector(`.igx-chip__remove`).focus();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@
</button>

<div #buttonsContainer class="igx-grid__filtering-row-editing-buttons">
<button igxButton igxRipple (click)="clearFiltering()" [disabled]="disabled">Reset</button>
<button igxButton igxRipple (click)="clearFiltering()" [disabled]="disabled" [tabindex]="disabled">Reset</button>
<button #closeButton igxButton igxRipple (click)="close()">Close</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
this.unaryConditionChanged.unsubscribe();
}

@HostListener('keydown.shift.tab', ['$event'])
@HostListener('keydown.tab', ['$event'])
public onTabKeydown(event) {
event.stopPropagation();
Expand Down Expand Up @@ -299,6 +300,9 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
public clearFiltering() {
this.filteringService.clearFilter(this.column.field);
this.resetExpression();
if (this.input) {
this.input.nativeElement.focus();
}
this.cdr.detectChanges();

this.chipAreaScrollOffset = 0;
Expand Down Expand Up @@ -338,6 +342,9 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
});
}

this.filteringService.updateFilteringCell(this.column.field);
this.filteringService.focusFilterCellChip(this.column.field, true);

this.filteringService.isFilterRowVisible = false;
this.filteringService.filteredColumn = null;
this.filteringService.selectedExpression = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ export class IgxFilteringService implements OnDestroy {
private filterPipe = new IgxGridFilterConditionPipe();
private titlecasePipe = new TitleCasePipe();
private datePipe = new DatePipe(window.navigator.language);
private columnStartIndex = -1;

public gridId: string;
public isFilterRowVisible = false;
public filteredColumn = null;
public selectedExpression: IFilteringExpression = null;
public columnToChipToFocus = new Map<string, boolean>();
public columnToFocus = null;
public shouldFocusNext = false;
public columnToMoreIconHidden = new Map<string, boolean>();
public columnStartIndex = -1;

constructor(private gridAPI: GridBaseAPIService<IgxGridBaseComponent>, private iconService: IgxIconService) {}

Expand Down Expand Up @@ -73,12 +74,12 @@ export class IgxFilteringService implements OnDestroy {
this.columnStartIndex = eventArgs.startIndex;
this.grid.filterCellList.forEach((filterCell) => {
filterCell.updateFilterCellArea();
if (filterCell.getChipToFocus()) {
this.columnToChipToFocus.set(filterCell.column.field, false);
filterCell.focusChip();
}
});
}
if (this.columnToFocus) {
this.focusFilterCellChip(this.columnToFocus.field, false);
this.columnToFocus = null;
}
});

this.grid.onColumnMovingEnd.pipe(takeUntil(this.destroy$)).subscribe((event) => {
Expand Down Expand Up @@ -260,13 +261,26 @@ export class IgxFilteringService implements OnDestroy {
}
}

private updateFilteringCell(columnId: string) {
/**
* Updates the content of a filterCell.
*/
public updateFilteringCell(columnId: string) {
const filterCell = this.grid.filterCellList.find(cell => cell.column.field === columnId);
if (filterCell) {
filterCell.updateFilterCellArea();
}
}

/**
* Focus a chip in a filterCell.
*/
public focusFilterCellChip(columnId: string, focusFirst: boolean) {
const filterCell = this.grid.filterCellList.find(cell => cell.column.field === columnId);
if (filterCell) {
filterCell.focusChip(focusFirst);
}
}

private isFilteringTreeComplex(expressions: IFilteringExpressionsTree | IFilteringExpression): boolean {
if (!expressions) {
return false;
Expand Down
47 changes: 30 additions & 17 deletions projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,35 @@ export class IgxGridNavigationService {
}

public isColumnFullyVisible(visibleColumnIndex: number) {
const horizontalScroll = this.grid.dataRowList.first.virtDirRow.getHorizontalScroll();
let forOfDir;
if (this.grid.dataRowList.length > 0) {
forOfDir = this.grid.dataRowList.first.virtDirRow;
} else {
forOfDir = this.grid.headerContainer;
}
const horizontalScroll = forOfDir.getHorizontalScroll();
if (!horizontalScroll.clientWidth ||
this.grid.columnList.filter(c => !c.columnGroup).find((column) => column.visibleIndex === visibleColumnIndex).pinned) {
return true;
}
const index = this.getColumnUnpinnedIndex(visibleColumnIndex);
return this.displayContainerWidth >=
this.grid.dataRowList.first.virtDirRow.getColumnScrollLeft(index + 1) -
this.displayContainerScrollLeft;
return this.displayContainerWidth >= forOfDir.getColumnScrollLeft(index + 1) - this.displayContainerScrollLeft;
}

public isColumnLeftFullyVisible(visibleColumnIndex) {
const horizontalScroll = this.grid.dataRowList.first.virtDirRow.getHorizontalScroll();
let forOfDir;
if (this.grid.dataRowList.length > 0) {
forOfDir = this.grid.dataRowList.first.virtDirRow;
} else {
forOfDir = this.grid.headerContainer;
}
const horizontalScroll = forOfDir.getHorizontalScroll();
if (!horizontalScroll.clientWidth ||
this.grid.columnList.filter(c => !c.columnGroup).find((column) => column.visibleIndex === visibleColumnIndex).pinned) {
return true;
}
const index = this.getColumnUnpinnedIndex(visibleColumnIndex);
return this.displayContainerScrollLeft <=
this.grid.dataRowList.first.virtDirRow.getColumnScrollLeft(index);
return this.displayContainerScrollLeft <= forOfDir.getColumnScrollLeft(index);
}

public get gridOrderedColumns(): IgxColumnComponent[] {
Expand Down Expand Up @@ -256,14 +265,6 @@ export class IgxGridNavigationService {

public navigateUp(rowElement, currentRowIndex, visibleColumnIndex) {
if (currentRowIndex === 0) {
this.grid.rowList.first.cells.first._clearCellSelection();

if (this.grid.allowFiltering) {
const visColLength = this.grid.visibleColumns.length;
this.grid.headerContainer.scrollTo(visColLength - 1);
this.grid.filteringService.columnToChipToFocus.set(this.grid.visibleColumns[visColLength - 1].field, true);
}

return;
}
const containerTopOffset = parseInt(this.verticalDisplayContainerElement.style.top, 10);
Expand Down Expand Up @@ -419,8 +420,20 @@ export class IgxGridNavigationService {
this.grid.rowEditTabs.last.element.nativeElement.focus();
return;
}
this.navigateUp(currentRowEl, rowIndex,
this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex);
if (rowIndex === 0 && this.grid.allowFiltering) {
this.grid.rowList.first.cells.first._clearCellSelection();
const visColLength = this.grid.visibleColumns.length;
if (this.grid.headerContainer.getItemCountInView() === visColLength) {
this.grid.filteringService.focusFilterCellChip(this.grid.filterCellList.last.column.field, false);
} else {
this.grid.filteringService.columnToFocus = this.grid.visibleColumns[visColLength - 1];
this.grid.filteringService.shouldFocusNext = false;
this.grid.headerContainer.scrollTo(visColLength - 1);
}
} else {
this.navigateUp(currentRowEl, rowIndex,
this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex);
}
} else {
const cell = currentRowEl.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
if (cell) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<div class="igx-grid__thead-item igx-grid__th--pinned">
<igx-grid-header [igxColumnMovingDrag]="col" [attr.droppable]="true" [igxColumnMovingDrop]="col" [gridID]="id"
[column]="col" [style.min-width.px]="col.width" [style.flex-basis.px]="col.width" [style.max-width.px]='col.width'></igx-grid-header>
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup && filteringService.filteredColumn !== col"
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup"
[column]="col" [style.min-width.px]="col.width" [style.flex-basis.px]='col.width' [style.max-width.px]='col.width'></igx-grid-filtering-cell>
</div>
</ng-template>
Expand All @@ -67,7 +67,7 @@
<div class="igx-grid__thead-item">
<igx-grid-header [igxColumnMovingDrag]="col" [attr.droppable]="true" [igxColumnMovingDrop]="col" [gridID]="id" [column]="col"
[style.min-width.px]="col.width" [style.flex-basis.px]='col.width' [style.max-width.px]='col.width'></igx-grid-header>
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup && filteringService.filteredColumn !== col" [column]="col"
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup" [column]="col"
[style.min-width.px]="col.width" [style.flex-basis.px]='col.width' [style.max-width.px]='col.width'></igx-grid-filtering-cell>
</div>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<div class="igx-grid__thead-item igx-grid__th--pinned">
<igx-grid-header [igxColumnMovingDrag]="col" [attr.droppable]="true" [igxColumnMovingDrop]="col" [gridID]="id"
[column]="col" [style.min-width.px]="col.width" [style.flex-basis.px]="col.width"></igx-grid-header>
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup && filteringService.filteredColumn !== col"
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup"
[column]="col" [style.min-width.px]="col.width" [style.flex-basis.px]='col.width' [style.max-width.px]='col.width'></igx-grid-filtering-cell>
</div>
</ng-template>
Expand All @@ -44,7 +44,7 @@
<div class="igx-grid__thead-item">
<igx-grid-header [igxColumnMovingDrag]="col" [attr.droppable]="true" [igxColumnMovingDrop]="col" [gridID]="id" [column]="col"
[style.min-width.px]="col.width" [style.flex-basis.px]='col.width'></igx-grid-header>
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup && filteringService.filteredColumn !== col"
<igx-grid-filtering-cell *ngIf="allowFiltering && !col.columnGroup"
[column]="col" [style.min-width.px]="col.width" [style.flex-basis.px]='col.width' [style.max-width.px]='col.width'></igx-grid-filtering-cell>
</div>
</ng-template>
Expand Down