Skip to content

Commit 68ec150

Browse files
committed
Merge branch 'pivot-grid-master' of https://github.com/IgniteUI/igniteui-angular into mkirova/fix-10644
2 parents 59067d1 + 5cca70a commit 68ec150

24 files changed

+692
-208
lines changed

projects/igniteui-angular/src/lib/data-operations/pivot-strategy.ts

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
import { GridType, PivotGridType } from '../grids/common/grid.interface';
3-
import { IPivotDimension, IPivotDimensionStrategy, IPivotKeys, IPivotValue, PivotDimensionType } from '../grids/pivot-grid/pivot-grid.interface';
3+
import { DEFAULT_PIVOT_KEYS, IPivotDimension, IPivotDimensionStrategy, IPivotKeys, IPivotValue, PivotDimensionType } from '../grids/pivot-grid/pivot-grid.interface';
44
import { PivotUtil } from '../grids/pivot-grid/pivot-util';
55
import { FilteringStrategy } from './filtering-strategy';
66
import { GridColumnDataType } from './data-util';
@@ -28,47 +28,57 @@ export class PivotRowDimensionsStrategy implements IPivotDimensionStrategy {
2828
}
2929

3030
public process(
31-
collection: any,
32-
rows: IPivotDimension[],
33-
values?: IPivotValue[],
34-
pivotKeys: IPivotKeys =
35-
{ aggregations: 'aggregations', records: 'records', children: 'children', level: 'level'}
36-
): any[] {
37-
let hierarchies;
38-
let data;
39-
const prevRowDims = [];
40-
let prevDim;
41-
const currRows = cloneArray(rows, true);
42-
PivotUtil.assignLevels(currRows);
43-
for (const row of currRows) {
44-
if (!data) {
45-
// build hierarchies - groups and subgroups
46-
hierarchies = PivotUtil.getFieldsHierarchy(collection, [row], PivotDimensionType.Row, pivotKeys);
47-
// generate flat data from the hierarchies
48-
data = PivotUtil.processHierarchy(hierarchies, collection[0] ?? [], pivotKeys, 0, true);
49-
prevRowDims.push(row);
50-
prevDim = row;
51-
} else {
52-
const newData = [...data];
53-
for (let i = 0; i < newData.length; i++) {
54-
const currData = newData[i][prevDim.memberName + '_' + pivotKeys.records];
55-
const leafData = PivotUtil.getDirectLeafs(currData, pivotKeys);
56-
const hierarchyFields = PivotUtil
57-
.getFieldsHierarchy(leafData, [row], PivotDimensionType.Row, pivotKeys);
58-
const siblingData = PivotUtil
59-
.processHierarchy(hierarchyFields, newData[i] ?? [], pivotKeys, 0);
60-
PivotUtil.processSiblingProperties(newData[i], siblingData, pivotKeys);
31+
collection: any,
32+
rows: IPivotDimension[],
33+
values?: IPivotValue[],
34+
pivotKeys: IPivotKeys = DEFAULT_PIVOT_KEYS
35+
): any[] {
36+
let hierarchies;
37+
let data;
38+
const prevRowDims = [];
39+
let prevDim;
40+
let prevDimTopRecords = [];
41+
const currRows = cloneArray(rows, true);
42+
PivotUtil.assignLevels(currRows);
43+
for (const row of currRows) {
44+
if (!data) {
45+
// build hierarchies - groups and subgroups
46+
hierarchies = PivotUtil.getFieldsHierarchy(collection, [row], PivotDimensionType.Row, pivotKeys);
47+
// generate flat data from the hierarchies
48+
data = PivotUtil.processHierarchy(hierarchies, collection[0] ?? [], pivotKeys, 0, true);
49+
prevRowDims.push(row);
50+
prevDim = row;
51+
prevDimTopRecords = data;
52+
} else {
53+
const newData = [...data];
54+
const curDimTopRecords = [];
55+
for (let i = 0; i < newData.length; i++) {
56+
const currData = newData[i][prevDim.memberName + '_' + pivotKeys.records];
57+
const leafData = PivotUtil.getDirectLeafs(currData, pivotKeys);
58+
const hierarchyFields = PivotUtil
59+
.getFieldsHierarchy(leafData, [row], PivotDimensionType.Row, pivotKeys);
60+
const siblingData = PivotUtil
61+
.processHierarchy(hierarchyFields, newData[i] ?? [], pivotKeys, 0);
62+
PivotUtil.processSiblingProperties(newData[i], siblingData, pivotKeys);
6163

6264
PivotUtil.processSubGroups(row, prevRowDims.slice(0), siblingData, pivotKeys);
63-
if (PivotUtil.getDimensionDepth(prevDim) > PivotUtil.getDimensionDepth(row) && siblingData.length > 1) {
65+
if ((prevDimTopRecords[i].length != undefined && prevDimTopRecords[i].length < siblingData.length) || prevDimTopRecords.length < siblingData.length) {
66+
// Add the sibling data as child records because the previous dimension contains more dense version of the previous dimension records.
6467
newData[i][row.memberName + '_' + pivotKeys.records] = siblingData;
6568
} else {
69+
// Replace the current record with the sibling records because the current dimension is a denser version or produces the same amount of records.
6670
newData.splice(i, 1, ...siblingData);
71+
// Shift the prevDimTopRecords item to the right because of the previous row transforms the newData and increases the elements in newData
72+
prevDimTopRecords.splice(siblingData.length, prevDimTopRecords.length - siblingData.length, ...prevDimTopRecords);
73+
// Increase the index the amount of sibling record that replaces the current one. Subtract 1 because there is already i++ in the for cycle.
6774
i += siblingData.length - 1;
6875
}
76+
// Add the current top sibling elements for the dimension
77+
curDimTopRecords.push(cloneArray(siblingData, true));
6978
}
7079
data = newData;
7180
prevDim = row;
81+
prevDimTopRecords = curDimTopRecords;
7282
prevRowDims.push(row);
7383
}
7484
}
@@ -87,7 +97,7 @@ export class PivotColumnDimensionsStrategy implements IPivotDimensionStrategy {
8797
collection: any[],
8898
columns: IPivotDimension[],
8999
values: IPivotValue[],
90-
pivotKeys: IPivotKeys = { aggregations: 'aggregations', records: 'records', children: 'children', level: 'level' }
100+
pivotKeys: IPivotKeys = DEFAULT_PIVOT_KEYS
91101
): any[] {
92102
const res = this.processHierarchy(collection, columns, values, pivotKeys);
93103
return res;

projects/igniteui-angular/src/lib/grids/headers/grid-header-group.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ export class IgxGridHeaderGroupComponent implements DoCheck {
324324
this.column.applySelectableClass = false;
325325
}
326326

327-
private get activeNode() {
327+
protected get activeNode() {
328328
return {
329329
row: -1, column: this.column.visibleIndex, level: this.column.level,
330330
mchCache: { level: this.column.level, visibleIndex: this.column.visibleIndex },

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@ export class IgxGridHeaderComponent implements DoCheck, OnDestroy {
213213
}
214214
}
215215
}
216-
this.grid.theadRow.nativeElement.focus();
216+
if (!this.grid.isPivot || !this.grid.navigation.isRowHeaderActive) {
217+
this.grid.theadRow.nativeElement.focus();
218+
}
217219
}
218220

219221
/**
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { TestBed, fakeAsync, ComponentFixture } from '@angular/core/testing';
2+
import { By } from '@angular/platform-browser';
3+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
4+
import { IgxPivotGridComponent, IgxPivotGridModule } from 'igniteui-angular';
5+
import { configureTestSuite } from '../../test-utils/configure-suite';
6+
import { GridFunctions } from '../../test-utils/grid-functions.spec';
7+
import { IgxPivotGridMultipleRowComponent } from '../../test-utils/pivot-grid-samples.spec';
8+
import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec';
9+
10+
const DEBOUNCE_TIME = 250;
11+
const PIVOT_TBODY_CSS_CLASS = '.igx-grid__tbody';
12+
const PIVOT_ROW_DIMENSION_CONTENT = 'igx-pivot-row-dimension-content';
13+
const PIVOT_HEADER_ROW = 'igx-pivot-header-row';
14+
const HEADER_CELL_CSS_CLASS = '.igx-grid-th';
15+
const ACTIVE_CELL_CSS_CLASS = '.igx-grid-th--active';
16+
17+
describe('IgxPivotGrid - Keyboard navigation #pivotGrid', () => {
18+
19+
let fixture: ComponentFixture<IgxPivotGridMultipleRowComponent>;
20+
let pivotGrid: IgxPivotGridComponent;
21+
configureTestSuite((() => {
22+
TestBed.configureTestingModule({
23+
declarations: [
24+
IgxPivotGridMultipleRowComponent
25+
],
26+
imports: [
27+
NoopAnimationsModule, IgxPivotGridModule]
28+
});
29+
}));
30+
31+
beforeEach(fakeAsync(() => {
32+
fixture = TestBed.createComponent(IgxPivotGridMultipleRowComponent);
33+
fixture.detectChanges();
34+
pivotGrid = fixture.componentInstance.pivotGrid;
35+
}));
36+
37+
it('should allow navigating between row headers', () => {
38+
const [firstCell, secondCell] = fixture.debugElement.queryAll(
39+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
40+
UIInteractions.simulateClickAndSelectEvent(firstCell);
41+
fixture.detectChanges();
42+
43+
GridFunctions.verifyHeaderIsFocused(firstCell.parent);
44+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
45+
expect(activeCells.length).toBe(1);
46+
47+
UIInteractions.triggerKeyDownEvtUponElem('ArrowRight', firstCell.nativeElement);
48+
fixture.detectChanges();
49+
GridFunctions.verifyHeaderIsFocused(secondCell.parent);
50+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
51+
expect(activeCells.length).toBe(1);
52+
});
53+
54+
it('should not go outside of the boundaries of the row dimensions content', () => {
55+
const [firstCell, _, thirdCell] = fixture.debugElement.queryAll(
56+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
57+
UIInteractions.simulateClickAndSelectEvent(firstCell);
58+
fixture.detectChanges();
59+
60+
UIInteractions.triggerKeyDownEvtUponElem('ArrowLeft', firstCell.nativeElement);
61+
fixture.detectChanges();
62+
63+
GridFunctions.verifyHeaderIsFocused(firstCell.parent);
64+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
65+
expect(activeCells.length).toBe(1);
66+
67+
UIInteractions.simulateClickAndSelectEvent(thirdCell);
68+
fixture.detectChanges();
69+
70+
UIInteractions.triggerKeyDownEvtUponElem('ArrowRight', thirdCell.nativeElement);
71+
fixture.detectChanges();
72+
73+
GridFunctions.verifyHeaderIsFocused(thirdCell.parent);
74+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
75+
expect(activeCells.length).toBe(1);
76+
});
77+
78+
it('should allow navigating from first to last row headers in a row(Home/End)', () => {
79+
const [firstCell, _, thirdCell] = fixture.debugElement.queryAll(
80+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
81+
UIInteractions.simulateClickAndSelectEvent(firstCell);
82+
fixture.detectChanges();
83+
84+
UIInteractions.triggerKeyDownEvtUponElem('End', firstCell.nativeElement);
85+
fixture.detectChanges();
86+
GridFunctions.verifyHeaderIsFocused(thirdCell.parent);
87+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
88+
expect(activeCells.length).toBe(1);
89+
90+
UIInteractions.triggerKeyDownEvtUponElem('Home', thirdCell.nativeElement);
91+
fixture.detectChanges();
92+
GridFunctions.verifyHeaderIsFocused(firstCell.parent);
93+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
94+
expect(activeCells.length).toBe(1);
95+
});
96+
97+
it('should allow navigating from first to last row headers(Ctrl + ArrowDown)', () => {
98+
const [_firstCell, _secondCell, thirdCell] = fixture.debugElement.queryAll(
99+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
100+
UIInteractions.simulateClickAndSelectEvent(thirdCell);
101+
fixture.detectChanges();
102+
103+
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', thirdCell.nativeElement, true, false, false, true);
104+
fixture.detectChanges();
105+
106+
const allCells = fixture.debugElement.queryAll(
107+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
108+
const lastCell = allCells[allCells.length - 1];
109+
GridFunctions.verifyHeaderIsFocused(lastCell.parent);
110+
const activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
111+
expect(activeCells.length).toBe(1);
112+
});
113+
114+
it('should allow navigating between column headers', () => {
115+
const [firstHeader, secondHeader] = fixture.debugElement.queryAll(
116+
By.css(`${PIVOT_HEADER_ROW} ${HEADER_CELL_CSS_CLASS}`));
117+
UIInteractions.simulateClickAndSelectEvent(firstHeader);
118+
fixture.detectChanges();
119+
120+
GridFunctions.verifyHeaderIsFocused(firstHeader.parent);
121+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
122+
expect(activeCells.length).toBe(1);
123+
124+
UIInteractions.triggerKeyDownEvtUponElem('ArrowRight', firstHeader.nativeElement);
125+
fixture.detectChanges();
126+
GridFunctions.verifyHeaderIsFocused(secondHeader.parent);
127+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
128+
expect(activeCells.length).toBe(1);
129+
});
130+
131+
it('should allow navigating from first to last column headers', async () => {
132+
const [firstHeader] = fixture.debugElement.queryAll(
133+
By.css(`${PIVOT_HEADER_ROW} ${HEADER_CELL_CSS_CLASS}`));
134+
UIInteractions.simulateClickAndSelectEvent(firstHeader);
135+
fixture.detectChanges();
136+
137+
GridFunctions.verifyHeaderIsFocused(firstHeader.parent);
138+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
139+
expect(activeCells.length).toBe(1);
140+
141+
UIInteractions.triggerKeyDownEvtUponElem('End', pivotGrid.theadRow.nativeElement);
142+
await wait(DEBOUNCE_TIME);
143+
fixture.detectChanges();
144+
145+
const allHeaders = fixture.debugElement.queryAll(
146+
By.css(`${PIVOT_HEADER_ROW} ${HEADER_CELL_CSS_CLASS}`));
147+
const lastHeader = allHeaders[allHeaders.length - 1];
148+
GridFunctions.verifyHeaderIsFocused(lastHeader.parent);
149+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
150+
expect(activeCells.length).toBe(1);
151+
});
152+
153+
it('should allow navigating in column headers when switching focus from rows to columns', () => {
154+
const [firstCell] = fixture.debugElement.queryAll(
155+
By.css(`${PIVOT_TBODY_CSS_CLASS} ${PIVOT_ROW_DIMENSION_CONTENT} ${HEADER_CELL_CSS_CLASS}`));
156+
UIInteractions.simulateClickAndSelectEvent(firstCell);
157+
fixture.detectChanges();
158+
159+
const [firstHeader, secondHeader] = fixture.debugElement.queryAll(
160+
By.css(`${PIVOT_HEADER_ROW} ${HEADER_CELL_CSS_CLASS}`));
161+
UIInteractions.simulateClickAndSelectEvent(firstHeader);
162+
fixture.detectChanges();
163+
164+
GridFunctions.verifyHeaderIsFocused(firstHeader.parent);
165+
let activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
166+
expect(activeCells.length).toBe(1);
167+
168+
UIInteractions.triggerKeyDownEvtUponElem('ArrowRight', firstHeader.nativeElement);
169+
fixture.detectChanges();
170+
GridFunctions.verifyHeaderIsFocused(secondHeader.parent);
171+
activeCells = fixture.debugElement.queryAll(By.css(`${ACTIVE_CELL_CSS_CLASS}`));
172+
expect(activeCells.length).toBe(1);
173+
});
174+
});
Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,53 @@
11
import { IgxGridNavigationService } from '../grid-navigation.service';
22
import { Injectable } from '@angular/core';
33
import { IgxPivotGridComponent } from './pivot-grid.component';
4+
import { HEADER_KEYS } from '../../core/utils';
45

56
@Injectable()
67
export class IgxPivotGridNavigationService extends IgxGridNavigationService {
78
public grid: IgxPivotGridComponent;
89

9-
public dispatchEvent(event: KeyboardEvent) {
10-
// TODO
10+
public isRowHeaderActive: boolean;
11+
12+
public get lastRowDimensionsIndex() {
13+
return this.grid.rowDimensions.length - 1;
14+
}
15+
16+
public focusOutRowHeader() {
17+
this.isRowHeaderActive = false;
18+
}
19+
20+
public handleNavigation(event: KeyboardEvent) {
21+
if (this.isRowHeaderActive) {
22+
const key = event.key.toLowerCase();
23+
const ctrl = event.ctrlKey;
24+
if (!HEADER_KEYS.has(key)) {
25+
return;
26+
}
27+
event.preventDefault();
28+
29+
const newActiveNode = {
30+
row: this.activeNode.row, column: this.activeNode.column, level: null,
31+
mchCache: null,
32+
layout: null
33+
}
34+
35+
if ((key.includes('left') || key === 'home') && this.activeNode.column > 0) {
36+
newActiveNode.column = ctrl || key === 'home' ? 0 : this.activeNode.column - 1;
37+
}
38+
if ((key.includes('right') || key === 'end') && this.activeNode.column < this.lastRowDimensionsIndex) {
39+
newActiveNode.column = ctrl || key === 'end' ? this.lastRowDimensionsIndex : this.activeNode.column + 1;
40+
}
41+
if ((key.includes('up')) && this.activeNode.row > 0) {
42+
newActiveNode.row = ctrl ? 0 : this.activeNode.row - 1;
43+
}
44+
if ((key.includes('down')) && this.activeNode.row < this.findLastDataRowIndex()) {
45+
newActiveNode.row = ctrl ? this.findLastDataRowIndex() : this.activeNode.row + 1;
46+
}
47+
this.setActiveNode(newActiveNode);
48+
this.grid.navigateTo(newActiveNode.row);
49+
} else {
50+
super.handleNavigation(event);
51+
}
1152
}
1253
}

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</igx-pivot-header-row>
2020

2121
<div igxGridBody (keydown.control.c)="copyHandler($event)" (copy)="copyHandler($event)" class="igx-grid__tbody" role="rowgroup">
22-
<div class="igx-grid__tbody-content" tabindex="0" [attr.role]="dataView.length ? null : 'row'" (keydown)="navigation.handleNavigation($event)"
22+
<div class="igx-grid__tbody-content" tabindex="0" [attr.role]="dataView.length ? null : 'row'" (keydown)="navigation.handleNavigation($event)" (focus)="navigation.focusTbody($event)"
2323
(dragStop)="selectionService.dragMode = $event" (scroll)='preventContainerScroll($event)'
2424
(dragScroll)="dragScroll($event)" [igxGridDragSelect]="selectionService.dragMode"
2525
[style.height.px]='totalHeight' [style.width.px]='calcWidth || null' #tbody [attr.aria-activedescendant]="activeDescendant">
@@ -119,7 +119,7 @@
119119
<div class="igx-grid__tr--header">
120120
<igx-icon [attr.draggable]="false"
121121
(click)="toggleColumn(column)">
122-
{{columnGroupStates.get(column.field) ? 'expand_more' : 'expand_less'}}</igx-icon>
122+
{{getColumnGroupExpandState(column) ? 'chevron_right' : 'expand_more'}}</igx-icon>
123123
{{column.header}}
124124
</div>
125125
</ng-template>

0 commit comments

Comments
 (0)