Skip to content

Commit b15cce3

Browse files
ddinchevazdrawku
authored andcommitted
Igx Tree Grid -- Keyboard Navigation (#2907)
feat(tree-grid): adding keyboard navigation
1 parent 88071bb commit b15cce3

15 files changed

+1207
-70
lines changed

projects/igniteui-angular/src/lib/grids/api.service.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent> {
229229
}
230230
}
231231

232-
public submit_value(gridId, detectChanges = true) {
232+
public submit_value(gridId) {
233233
const editableCell = this.get_cell_inEditMode(gridId);
234234
if (editableCell) {
235235
if (!editableCell.cell.column.inlineEditorTemplate && editableCell.cell.column.dataType === 'number') {
@@ -245,9 +245,6 @@ export class GridBaseAPIService <T extends IgxGridBaseComponent> {
245245
this.update_cell(gridId, editableCell.cellID.rowID, editableCell.cellID.columnID, editableCell.cell.editValue);
246246
}
247247
this.escape_editMode(gridId, editableCell.cellID);
248-
if (detectChanges) {
249-
this.get(gridId).cdr.detectChanges();
250-
}
251248
}
252249
}
253250

projects/igniteui-angular/src/lib/grids/cell.component.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
ViewChild
1313
} from '@angular/core';
1414
import { IgxSelectionAPIService } from '../core/selection';
15-
import { DataType } from '../data-operations/data-util';
1615
import { IgxTextHighlightDirective } from '../directives/text-highlight/text-highlight.directive';
1716
import { GridBaseAPIService } from './api.service';
1817
import { IgxColumnComponent } from './column.component';
1918
import { isNavigationKey, valToPxlsUsingRange } from '../core/utils';
2019
import { State } from '../services/index';
2120
import { IgxGridBaseComponent } from './grid-base.component';
22-
21+
import { first } from 'rxjs/operators';
2322
/**
2423
* Providing reference to `IgxGridCellComponent`:
2524
* ```typescript
@@ -713,6 +712,21 @@ export class IgxGridCellComponent implements OnInit, AfterViewInit {
713712
event.stopPropagation();
714713
}
715714

715+
if (event.altKey) {
716+
if (this.row.nativeElement.tagName.toLowerCase() === 'igx-tree-grid-row' && this.isToggleKey(key)) {
717+
const collapse = (this.row as any).expanded && (key === 'left' || key === 'arrowleft');
718+
const expand = !(this.row as any).expanded && (key === 'right' || key === 'arrowright');
719+
if (collapse) {
720+
(this.gridAPI as any).trigger_row_expansion_toggle(
721+
this.gridID, this.row.treeRow, !this.row.expanded, event, this.visibleColumnIndex);
722+
} else if (expand) {
723+
(this.gridAPI as any).trigger_row_expansion_toggle(
724+
this.gridID, this.row.treeRow, !this.row.expanded, event, this.visibleColumnIndex);
725+
}
726+
return;
727+
}
728+
}
729+
716730
const args = {cell: this, groupRow: null, event: event, cancel: false };
717731
this.grid.onFocusChange.emit(args);
718732
if (args.cancel) {
@@ -878,4 +892,8 @@ export class IgxGridCellComponent implements OnInit, AfterViewInit {
878892
return Math.max(...Array.from(this.nativeElement.children)
879893
.map((child) => valToPxlsUsingRange(range, child)));
880894
}
895+
896+
private isToggleKey(key) {
897+
return ['left', 'right', 'arrowleft', 'arrowright'].indexOf(key.toLowerCase()) !== -1;
898+
}
881899
}

projects/igniteui-angular/src/lib/grids/grid-base.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,7 +1966,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
19661966

19671967
private keydownHandler(event) {
19681968
const key = event.key.toLowerCase();
1969-
if (isNavigationKey(key) || key === 'tab' || key === 'pagedown' || key === 'pageup') {
1969+
if ((isNavigationKey(key) && event.keyCode !== 32) || key === 'tab' || key === 'pagedown' || key === 'pageup') {
19701970
event.preventDefault();
19711971
if (key === 'pagedown') {
19721972
this.verticalScrollContainer.scrollNextPage();
@@ -4321,7 +4321,7 @@ export abstract class IgxGridBaseComponent implements OnInit, OnDestroy, AfterCo
43214321
const rowObject = row ? this.getRowByKey(row.rowID) : null;
43224322
const cellInEdit = this.gridAPI.get_cell_inEditMode(this.id);
43234323
if (cellInEdit) {
4324-
this.gridAPI.submit_value(this.id, commit);
4324+
this.gridAPI.submit_value(this.id);
43254325
}
43264326
this.endRowTransaction(commit, true, row, rowObject);
43274327
const currentCell = (row && cellInEdit) ? this.gridAPI.get_cell_by_index(this.id, row.rowIndex, cellInEdit.cellID.columnID) : null;

projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export class IgxGridNavigationService {
7979
}
8080

8181
public getCellElementByVisibleIndex(rowIndex, visibleColumnIndex) {
82+
if (this.isTreeGrid && visibleColumnIndex === 0) {
83+
return this.grid.nativeElement.querySelector(
84+
`igx-tree-grid-cell[data-rowindex="${rowIndex}"][data-visibleIndex="${visibleColumnIndex}"]`);
85+
}
8286
return this.grid.nativeElement.querySelector(
8387
`igx-grid-cell[data-rowindex="${rowIndex}"][data-visibleIndex="${visibleColumnIndex}"]`);
8488
}
@@ -174,14 +178,18 @@ export class IgxGridNavigationService {
174178
}
175179
public onKeydownHome(rowIndex) {
176180
const rowElement = this.grid.dataRowList.find((row) => row.index === rowIndex).nativeElement;
177-
const firstCell = rowElement.querySelector('igx-grid-cell');
181+
let firstCell = this.isTreeGrid ?
182+
rowElement.querySelector('igx-tree-grid-cell') :
183+
rowElement.querySelector('igx-grid-cell');
178184
if (this.grid.pinnedColumns.length || this.displayContainerScrollLeft === 0) {
179185
firstCell.focus();
180186
} else {
181187
this.grid.parentVirtDir.onChunkLoad
182188
.pipe(first())
183189
.subscribe(() => {
184190
this.grid.nativeElement.focus({preventScroll: true});
191+
firstCell = this.isTreeGrid ? rowElement.querySelector('igx-tree-grid-cell') :
192+
rowElement.querySelector('igx-grid-cell');
185193
firstCell.focus();
186194
});
187195
this.horizontalScroll(rowIndex).scrollTo(0);
@@ -208,35 +216,37 @@ export class IgxGridNavigationService {
208216

209217
public navigateTop(visibleColumnIndex) {
210218
const verticalScroll = this.grid.verticalScrollContainer.getVerticalScroll();
219+
const cellSelector = this.isTreeGrid && visibleColumnIndex === 0 ? 'igx-tree-grid-cell' : 'igx-grid-cell';
211220
if (verticalScroll.scrollTop === 0) {
212221
const cells = this.grid.nativeElement.querySelectorAll(
213-
`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
222+
`${cellSelector}[data-visibleIndex="${visibleColumnIndex}"]`);
214223
cells[0].focus();
215224
} else {
216225
this.grid.nativeElement.focus({preventScroll: true});
217226
this.grid.verticalScrollContainer.scrollTo(0);
218227
this.grid.verticalScrollContainer.onChunkLoad
219228
.pipe(first()).subscribe(() => {
220229
const cells = this.grid.nativeElement.querySelectorAll(
221-
`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
222-
if (cells.length > 0) { cells[0].focus(); }
230+
`${cellSelector}[data-visibleIndex="${visibleColumnIndex}"]`);
231+
if (cells.length > 0) { cells[0].focus(); }
223232
});
224233
}
225234
}
226235

227236
public navigateBottom(visibleColumnIndex) {
228237
const verticalScroll = this.grid.verticalScrollContainer.getVerticalScroll();
238+
const cellSelector = this.isTreeGrid && visibleColumnIndex === 0 ? 'igx-tree-grid-cell' : 'igx-grid-cell';
229239
if (verticalScroll.scrollTop === verticalScroll.scrollHeight - this.grid.verticalScrollContainer.igxForContainerSize) {
230240
const cells = this.grid.nativeElement.querySelectorAll(
231-
`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
241+
`${cellSelector}[data-visibleIndex="${visibleColumnIndex}"]`);
232242
cells[cells.length - 1].focus();
233243
} else {
234244
this.grid.nativeElement.focus({preventScroll: true});
235245
this.grid.verticalScrollContainer.scrollTo(this.grid.verticalScrollContainer.igxForOf.length - 1);
236246
this.grid.verticalScrollContainer.onChunkLoad
237247
.pipe(first()).subscribe(() => {
238248
const cells = this.grid.nativeElement.querySelectorAll(
239-
`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
249+
`${cellSelector}[data-visibleIndex="${visibleColumnIndex}"]`);
240250
if (cells.length > 0) { cells[cells.length - 1].focus(); }
241251
});
242252
}
@@ -254,9 +264,9 @@ export class IgxGridNavigationService {
254264
this.grid.verticalScrollContainer.onChunkLoad
255265
.pipe(first())
256266
.subscribe(() => {
257-
if (rowElement.tagName.toLowerCase() === 'igx-grid-row') {
258-
rowElement = this.grid.nativeElement.querySelector(
259-
`igx-grid-row[data-rowindex="${currentRowIndex}"]`);
267+
const tag = rowElement.tagName.toLowerCase();
268+
if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row') {
269+
rowElement = this.getRowByIndex(currentRowIndex);
260270
} else {
261271
rowElement = this.grid.nativeElement.querySelector(
262272
`igx-grid-groupby-row[data-rowindex="${currentRowIndex}"]`);
@@ -273,7 +283,10 @@ export class IgxGridNavigationService {
273283
currentRowEl.previousElementSibling.focus();
274284
} else {
275285
if (this.isColumnFullyVisible(visibleColumnIndex) && this.isColumnLeftFullyVisible(visibleColumnIndex)) {
276-
currentRowEl.previousElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`).focus();
286+
const cell = this.isTreeGrid && visibleColumnIndex === 0 ?
287+
currentRowEl.previousElementSibling.querySelector(`igx-tree-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`) :
288+
currentRowEl.previousElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
289+
cell.focus();
277290
return;
278291
}
279292
this.grid.nativeElement.focus({preventScroll: true});
@@ -297,11 +310,11 @@ export class IgxGridNavigationService {
297310
this.grid.verticalScrollContainer.onChunkLoad
298311
.pipe(first())
299312
.subscribe(() => {
300-
if (rowElement.tagName.toLowerCase() === 'igx-grid-row') {
301-
rowElement = this.grid.nativeElement.querySelector(
302-
`igx-grid-row[data-rowindex="${currentRowIndex}"]`);
313+
const tag = rowElement.tagName.toLowerCase();
314+
if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row') {
315+
rowElement = this.getRowByIndex(currentRowIndex);
303316
} else {
304-
rowElement = rowElement = this.grid.nativeElement.querySelector(
317+
rowElement = this.grid.nativeElement.querySelector(
305318
`igx-grid-groupby-row[data-rowindex="${currentRowIndex}"]`);
306319
}
307320
this.focusNextElement(rowElement, visibleColumnIndex);
@@ -316,7 +329,10 @@ export class IgxGridNavigationService {
316329
rowElement.nextElementSibling.focus();
317330
} else {
318331
if (this.isColumnFullyVisible(visibleColumnIndex) && this.isColumnLeftFullyVisible(visibleColumnIndex)) {
319-
rowElement.nextElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`).focus();
332+
const cell = this.isTreeGrid && visibleColumnIndex === 0 ?
333+
rowElement.nextElementSibling.querySelector(`igx-tree-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`) :
334+
rowElement.nextElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
335+
cell.focus();
320336
return;
321337
}
322338
this.performHorizontalScrollToCell(parseInt(
@@ -328,7 +344,11 @@ export class IgxGridNavigationService {
328344
const verticalScroll = this.grid.verticalScrollContainer.getVerticalScroll();
329345
const horizontalScroll = this.grid.dataRowList.first.virtDirRow.getHorizontalScroll();
330346
if (verticalScroll.scrollTop === 0) {
331-
this.onKeydownHome(this.grid.dataRowList.first.index);
347+
if (!this.isTreeGrid) {
348+
this.onKeydownHome(this.grid.dataRowList.first.index);
349+
} else {
350+
this.onKeydownHome(0);
351+
}
332352
} else {
333353
if (!horizontalScroll.clientWidth || parseInt(horizontalScroll.scrollLeft, 10) <= 1 || this.grid.pinnedColumns.length) {
334354
this.navigateTop(0);
@@ -346,14 +366,14 @@ export class IgxGridNavigationService {
346366
public goToLastCell() {
347367
const verticalScroll = this.grid.verticalScrollContainer.getVerticalScroll();
348368
if (verticalScroll.scrollTop === verticalScroll.scrollHeight - this.grid.verticalScrollContainer.igxForContainerSize) {
349-
const rows = this.grid.nativeElement.querySelectorAll('igx-grid-row');
369+
const rows = this.getAllRows();
350370
const rowIndex = parseInt(rows[rows.length - 1].getAttribute('data-rowIndex'), 10);
351371
this.onKeydownEnd(rowIndex);
352372
} else {
353373
this.grid.verticalScrollContainer.scrollTo(this.grid.verticalScrollContainer.igxForOf.length - 1);
354374
this.grid.verticalScrollContainer.onChunkLoad
355375
.pipe(first()).subscribe(() => {
356-
const rows = this.grid.nativeElement.querySelectorAll('igx-grid-row');
376+
const rows = this.getAllRows();
357377
if (rows.length > 0) {
358378
const rowIndex = parseInt(rows[rows.length - 1].getAttribute('data-rowIndex'), 10);
359379
this.onKeydownEnd(rowIndex);
@@ -372,7 +392,7 @@ export class IgxGridNavigationService {
372392
this.navigateDown(currentRowEl, rowIndex, 0);
373393
}
374394
} else {
375-
const cell = currentRowEl.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`);
395+
const cell = this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex);
376396
if (cell) {
377397
if (this.grid.rowEditable && this.isRowInEditMode(rowIndex)) {
378398
this.moveNextEditable(cell, rowIndex, visibleColumnIndex);
@@ -412,4 +432,19 @@ export class IgxGridNavigationService {
412432
});
413433
this.horizontalScroll(rowIndex).scrollTo(unpinnedIndex);
414434
}
435+
private getRowByIndex(index) {
436+
return this.isTreeGrid ? this.grid.nativeElement.querySelector(
437+
`igx-tree-grid-row[data-rowindex="${index}"]`) :
438+
this.grid.nativeElement.querySelector(
439+
`igx-grid-row[data-rowindex="${index}"]`);
440+
}
441+
442+
private getAllRows() {
443+
return this.isTreeGrid ? this.grid.nativeElement.querySelectorAll('igx-tree-grid-row') :
444+
this.grid.nativeElement.querySelectorAll('igx-grid-row');
445+
}
446+
447+
private get isTreeGrid() {
448+
return this.grid.nativeElement.tagName.toLowerCase() === 'igx-tree-grid';
449+
}
415450
}

projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ describe('IgxGrid - Cell component', () => {
259259
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomPK.nativeElement, true);
260260
await wait(DEBOUNCETIME);
261261

262+
fixture.detectChanges();
262263
expect(cell.inEditMode).toBe(false);
263264
expect(cell.value).toBe(87);
264265
}));
@@ -279,6 +280,7 @@ describe('IgxGrid - Cell component', () => {
279280
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomNumber.nativeElement, true);
280281
await wait(DEBOUNCETIME);
281282

283+
fixture.detectChanges();
282284
expect(cell.inEditMode).toBe(false);
283285
expect(parseFloat(cell.value)).toBe(0.3698);
284286
expect(editTemplate.nativeElement.type).toBe('number');
@@ -300,6 +302,7 @@ describe('IgxGrid - Cell component', () => {
300302
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomNumber.nativeElement, true);
301303
await wait(DEBOUNCETIME);
302304

305+
fixture.detectChanges();
303306
expect(cell.inEditMode).toBe(false);
304307
expect(parseFloat(cell.value)).toBe(expectedValue);
305308

@@ -312,6 +315,7 @@ describe('IgxGrid - Cell component', () => {
312315
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomNumber.nativeElement, true);
313316
await wait(DEBOUNCETIME);
314317

318+
fixture.detectChanges();
315319
expect(cell.inEditMode).toBe(false);
316320
expect(parseFloat(cell.value)).toBe(expectedValue);
317321
}));
@@ -335,6 +339,7 @@ describe('IgxGrid - Cell component', () => {
335339
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomBoolean.nativeElement, true);
336340
await wait(DEBOUNCETIME);
337341

342+
fixture.detectChanges();
338343
expect(cell.inEditMode).toBe(false);
339344
expect(cell.value).toBe(false);
340345
}));
@@ -358,6 +363,7 @@ describe('IgxGrid - Cell component', () => {
358363
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDomDate.nativeElement, true);
359364
await wait(DEBOUNCETIME);
360365

366+
fixture.detectChanges();
361367
expect(cell.inEditMode).toBe(false);
362368
expect(cell.value).toBe(selectedDate);
363369
}));
@@ -441,6 +447,7 @@ describe('IgxGrid - Cell component', () => {
441447
UIInteractions.triggerKeyDownEvtUponElem('enter', cellDom.nativeElement, true);
442448
await wait(DEBOUNCETIME);
443449

450+
fixture.detectChanges();
444451
expect(cell.value).toBe('Rick Gilmore');
445452
expect(cell.gridAPI.get_cell_inEditMode(cell.gridID)).toBeNull();
446453
}));

0 commit comments

Comments
 (0)