Skip to content

Commit 58669a8

Browse files
committed
fix(igxGrid): Fixes for row pinning and navigation. Adding automation.
1 parent 8c9a572 commit 58669a8

File tree

6 files changed

+259
-22
lines changed

6 files changed

+259
-22
lines changed

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,9 +2666,9 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
26662666
*/
26672667
public getDataViewIndex(rowIndex, pinned) {
26682668
if (pinned && !this.isRowPinningToTop) {
2669-
rowIndex = rowIndex + this.unpinnedRecords.length;
2669+
rowIndex = rowIndex + this.unpinnedDataView.length;
26702670
} else if (!pinned && this.isRowPinningToTop) {
2671-
rowIndex = rowIndex + this.pinnedRecordsCount;
2671+
rowIndex = rowIndex + this.pinnedDataView.length;
26722672
}
26732673
return rowIndex;
26742674
}
@@ -2730,8 +2730,8 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
27302730
* @internal
27312731
*/
27322732
public isRecordPinnedByIndex(rowIndex: number) {
2733-
return this.hasPinnedRecords && (this.isRowPinningToTop && rowIndex < this.pinnedRecordsCount) ||
2734-
(!this.isRowPinningToTop && rowIndex >= this.unpinnedRecords.length);
2733+
return this.hasPinnedRecords && (this.isRowPinningToTop && rowIndex < this.pinnedDataView.length) ||
2734+
(!this.isRowPinningToTop && rowIndex >= this.unpinnedDataView.length);
27352735
}
27362736

27372737
/**
@@ -3004,6 +3004,7 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
30043004
public setFilteredSortedData(data, pinned: boolean) {
30053005
if (this._pinnedRecordIDs.length > 0 && pinned) {
30063006
this._filteredSortedPinnedData = data;
3007+
this.pinnedRecords = data;
30073008
this.filteredSortedData = this.isRowPinningToTop ? [... this._filteredSortedPinnedData, ... this._filteredSortedUnpinnedData] :
30083009
[... this._filteredSortedUnpinnedData, ... this._filteredSortedPinnedData];
30093010
} else if (this._pinnedRecordIDs.length > 0 && !pinned) {
@@ -5648,13 +5649,10 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
56485649
visibleColIndex = -1;
56495650
}
56505651
// If the target row is pinned no need to scroll as well.
5651-
const shouldScrollVertically =
5652-
!this.isRecordPinnedByIndex(rowIndex) && this.navigation.shouldPerformVerticalScroll(rowIndex, visibleColIndex);
5652+
const shouldScrollVertically = this.navigation.shouldPerformVerticalScroll(rowIndex, visibleColIndex);
56535653
const shouldScrollHorizontally = this.navigation.shouldPerformHorizontalScroll(visibleColIndex, rowIndex);
56545654
if (shouldScrollVertically) {
5655-
// Only for top pinning we need to subtract pinned count because virtualization indexing doesn't count pinned rows.
5656-
const scrollRowIndex = this.isRowPinningToTop ? rowIndex - this.pinnedRecordsCount : rowIndex;
5657-
this.navigation.performVerticalScrollToCell(scrollRowIndex, visibleColIndex,
5655+
this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex,
56585656
() => { this.navigateTo(rowIndex, visibleColIndex, cb); });
56595657
} else if (shouldScrollHorizontally) {
56605658
this.navigation.performHorizontalScrollToCell(visibleColIndex, () => { this.navigateTo(rowIndex, visibleColIndex, cb); });

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,11 @@ export class IgxGridNavigationService {
314314
}
315315

316316
public shouldPerformVerticalScroll(targetRowIndex: number, visibleColIndex: number): boolean {
317+
if (this.grid.isRecordPinnedByIndex(targetRowIndex)) { return false; }
318+
const scrollRowIndex = this.grid.hasPinnedRecords && this.grid.isRowPinningToTop ?
319+
targetRowIndex - this.grid.pinnedDataView.length : targetRowIndex;
317320
const targetRow = this.getRowElementByIndex(targetRowIndex);
318-
const rowHeight = this.grid.verticalScrollContainer.getSizeAt(targetRowIndex);
321+
const rowHeight = this.grid.verticalScrollContainer.getSizeAt(scrollRowIndex);
319322
const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0;
320323
const endTopOffset = targetRow ? targetRow.offsetTop + rowHeight + this.containerTopOffset : containerHeight + rowHeight;
321324
return !targetRow || targetRow.offsetTop < Math.abs(this.containerTopOffset)
@@ -328,7 +331,10 @@ export class IgxGridNavigationService {
328331
}
329332

330333
public performVerticalScrollToCell(rowIndex: number, visibleColIndex = -1, cb?: () => void) {
331-
this.grid.verticalScrollContainer.scrollTo(rowIndex);
334+
// Only for top pinning we need to subtract pinned count because virtualization indexing doesn't count pinned rows.
335+
const scrollRowIndex = this.grid.hasPinnedRecords && this.grid.isRowPinningToTop ?
336+
rowIndex - this.grid.pinnedDataView.length : rowIndex;
337+
this.grid.verticalScrollContainer.scrollTo(scrollRowIndex);
332338
this.grid.verticalScrollContainer.onChunkLoad
333339
.pipe(first()).subscribe(() => {
334340
if (cb) { cb(); }

projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,18 @@ export class IgxGridRowPinningPipe implements PipeTransform {
166166
public transform(collection: any[] , id: string, isPinned = false, pipeTrigger: number) {
167167
const grid = this.gridAPI.grid;
168168

169-
if (!grid.hasPinnedRecords) {
170-
return isPinned ? [] : collection;
171-
}
172-
173169
if (grid.hasPinnedRecords && isPinned) {
174170
const result = collection.filter(rec => grid.isRecordPinned(rec));
175171
result.sort((rec1, rec2) => grid.pinRecordIndex(rec1) - grid.pinRecordIndex(rec2));
176-
grid.pinnedRecords = result;
177172
return result;
178173
}
179174

180175
grid.unpinnedRecords = collection;
176+
if (!grid.hasPinnedRecords) {
177+
grid.pinnedRecords = [];
178+
return isPinned ? [] : collection;
179+
}
180+
181181
return collection.map((rec) => {
182182
return grid.isRecordPinned(rec) ? { recordRef: rec, ghostRecord: true} : rec;
183183
});

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

Lines changed: 234 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ import { configureTestSuite } from '../../test-utils/configure-suite';
88
import { ColumnPinningPosition, RowPinningPosition } from '../common/enums';
99
import { IPinningConfig } from '../common/grid.interface';
1010
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
11-
import { verifyLayoutHeadersAreAligned, verifyDOMMatchesLayoutSettings } from '../../test-utils/helper-utils.spec';
11+
import {
12+
verifyLayoutHeadersAreAligned,
13+
verifyDOMMatchesLayoutSettings,
14+
setupGridScrollDetection
15+
} from '../../test-utils/helper-utils.spec';
1216
import { GridFunctions } from '../../test-utils/grid-functions.spec';
1317
import { SortingDirection } from '../../data-operations/sorting-expression.interface';
1418
import { IgxGridTransaction } from '../tree-grid';
1519
import { IgxTransactionService } from '../../services';
1620
import { GridSummaryFunctions } from '../../test-utils/grid-functions.spec';
1721
import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition';
1822
import { IgxPaginatorComponent } from '../../paginator/paginator.component';
19-
import { wait } from '../../test-utils/ui-interactions.spec';
23+
import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec';
2024

2125
describe('Row Pinning #grid', () => {
2226
const FIXED_ROW_CONTAINER = '.igx-grid__tr--pinned ';
@@ -439,7 +443,7 @@ describe('Row Pinning #grid', () => {
439443
pinRowContainer = fix.debugElement.queryAll(By.css(FIXED_ROW_CONTAINER));
440444
expect(pinRowContainer.length).toBe(0);
441445

442-
expect(grid.dataView.length).toBe(6);
446+
expect(grid.dataView.length).toBe(5);
443447
expect(paginator.componentInstance.totalPages).toEqual(6);
444448
});
445449

@@ -774,6 +778,233 @@ describe('Row Pinning #grid', () => {
774778
expect(grid.calcHeight - expectedHeight).toBeLessThanOrEqual(1);
775779
});
776780
});
781+
782+
describe(' Navigation', () => {
783+
let gridContent: DebugElement;
784+
785+
beforeEach(() => {
786+
fix = TestBed.createComponent(GridRowPinningComponent);
787+
fix.detectChanges();
788+
grid = fix.componentInstance.instance;
789+
setupGridScrollDetection(fix, grid);
790+
gridContent = GridFunctions.getGridContent(fix);
791+
});
792+
793+
it('should navigate to bottom from top pinned row using Ctrl+ArrowDown', async() => {
794+
grid.getRowByIndex(5).pin();
795+
fix.detectChanges();
796+
797+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
798+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
799+
fix.detectChanges();
800+
801+
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, false, false, true);
802+
await wait(DEBOUNCE_TIME);
803+
fix.detectChanges();
804+
await wait(DEBOUNCE_TIME);
805+
fix.detectChanges();
806+
807+
const lastRowCell = grid.getRowByIndex(27).cells.toArray()[1];
808+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
809+
expect(selectedCell).toBe(lastRowCell);
810+
expect(selectedCell.rowIndex).toBe(27);
811+
});
812+
813+
it('should navigate and scroll to first unpinned row from top pinned row using ArrowDown', async() => {
814+
grid.getRowByIndex(5).pin();
815+
fix.detectChanges();
816+
817+
grid.navigateTo(10);
818+
await wait(DEBOUNCE_TIME);
819+
fix.detectChanges();
820+
821+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
822+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
823+
fix.detectChanges();
824+
825+
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent);
826+
await wait(DEBOUNCE_TIME);
827+
fix.detectChanges();
828+
await wait(DEBOUNCE_TIME);
829+
fix.detectChanges();
830+
831+
const secondRowCell = grid.getRowByIndex(1).cells.toArray()[1];
832+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
833+
expect(selectedCell).toBe(secondRowCell);
834+
expect(selectedCell.rowIndex).toBe(1);
835+
});
836+
837+
it('should navigate to top pinned row from bottom unpinned row without scrolling using Ctrl+ArrowUp', async() => {
838+
grid.getRowByIndex(5).pin();
839+
fix.detectChanges();
840+
841+
grid.navigateTo(27);
842+
await wait(DEBOUNCE_TIME);
843+
fix.detectChanges();
844+
845+
expect(grid.verticalScrollContainer.getScroll().scrollTop).not.toEqual(0);
846+
847+
const lastRowCell = grid.getRowByIndex(27).cells.toArray()[1];
848+
UIInteractions.simulateClickAndSelectCellEvent(lastRowCell);
849+
fix.detectChanges();
850+
851+
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, false, false, true);
852+
await wait(DEBOUNCE_TIME);
853+
fix.detectChanges();
854+
await wait(DEBOUNCE_TIME);
855+
fix.detectChanges();
856+
857+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
858+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
859+
expect(selectedCell).toBe(firstRowCell);
860+
expect(selectedCell.rowIndex).toBe(0);
861+
expect(grid.verticalScrollContainer.getScroll().scrollTop).not.toEqual(0);
862+
});
863+
864+
it('should navigate to top pinned row from first unpinned row using ArrowUp', async() => {
865+
grid.getRowByIndex(5).pin();
866+
grid.getRowByIndex(1).pin();
867+
fix.detectChanges();
868+
869+
const thirdRowCell = grid.getRowByIndex(2).cells.toArray()[1];
870+
UIInteractions.simulateClickAndSelectCellEvent(thirdRowCell);
871+
fix.detectChanges();
872+
873+
expect(grid.navigation.activeNode.row).toBe(2);
874+
expect(grid.navigation.activeNode.column).toBe(1);
875+
876+
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent);
877+
await wait(DEBOUNCE_TIME);
878+
fix.detectChanges();
879+
880+
const secondRowCell = grid.getRowByIndex(1).cells.toArray()[1];
881+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
882+
expect(selectedCell).toBe(secondRowCell);
883+
expect(selectedCell.rowIndex).toBe(1);
884+
});
885+
886+
it('should navigate and scroll to top from bottom pinned row using Ctrl+ArrowUp', async() => {
887+
fix.componentInstance.pinningConfig = { columns: ColumnPinningPosition.Start, rows: RowPinningPosition.Bottom };
888+
grid.getRowByIndex(5).pin();
889+
fix.detectChanges();
890+
891+
grid.navigateTo(26);
892+
await wait(DEBOUNCE_TIME);
893+
fix.detectChanges();
894+
895+
const lastRowCell = grid.getRowByIndex(27).cells.toArray()[1];
896+
UIInteractions.simulateClickAndSelectCellEvent(lastRowCell);
897+
fix.detectChanges();
898+
899+
expect(grid.navigation.activeNode.row).toBe(27);
900+
expect(grid.navigation.activeNode.column).toBe(1);
901+
902+
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, false, false, true);
903+
await wait(DEBOUNCE_TIME);
904+
fix.detectChanges();
905+
await wait(DEBOUNCE_TIME);
906+
fix.detectChanges();
907+
908+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
909+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
910+
expect(selectedCell).toBe(firstRowCell);
911+
expect(selectedCell.rowIndex).toBe(0);
912+
});
913+
914+
it('should navigate to last unpinned row from bottom pinned row using ArrowUp', async() => {
915+
fix.componentInstance.pinningConfig = { columns: ColumnPinningPosition.Start, rows: RowPinningPosition.Bottom };
916+
grid.getRowByIndex(5).pin();
917+
fix.detectChanges();
918+
919+
const firstRowCell = grid.getRowByIndex(27).cells.toArray()[1];
920+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
921+
fix.detectChanges();
922+
923+
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent);
924+
await wait(DEBOUNCE_TIME);
925+
fix.detectChanges();
926+
await wait(DEBOUNCE_TIME);
927+
fix.detectChanges();
928+
929+
const lastUnpinnedRowCell = grid.getRowByIndex(26).cells.toArray()[1];
930+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
931+
expect(selectedCell).toBe(lastUnpinnedRowCell);
932+
expect(selectedCell.rowIndex).toBe(26);
933+
});
934+
935+
it('should navigate to bottom pinned row from top unpinned row without scrolling using Ctrl+ArrowDown', async() => {
936+
fix.componentInstance.pinningConfig = { columns: ColumnPinningPosition.Start, rows: RowPinningPosition.Bottom };
937+
grid.getRowByIndex(5).pin();
938+
fix.detectChanges();
939+
940+
expect(grid.verticalScrollContainer.getScroll().scrollTop).toEqual(0);
941+
942+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
943+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
944+
fix.detectChanges();
945+
946+
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, false, false, true);
947+
await wait(DEBOUNCE_TIME);
948+
fix.detectChanges();
949+
await wait(DEBOUNCE_TIME);
950+
fix.detectChanges();
951+
952+
const lastRowCell = grid.getRowByIndex(27).cells.toArray()[1];
953+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
954+
expect(selectedCell).toBe(lastRowCell);
955+
expect(selectedCell.rowIndex).toBe(27);
956+
expect(grid.verticalScrollContainer.getScroll().scrollTop).toEqual(0);
957+
});
958+
959+
it('should navigate to bottom pinned row from last unpinned row using ArrowDown', async() => {
960+
fix.componentInstance.pinningConfig = { columns: ColumnPinningPosition.Start, rows: RowPinningPosition.Bottom };
961+
grid.getRowByIndex(5).pin();
962+
grid.getRowByIndex(1).pin();
963+
fix.detectChanges();
964+
965+
grid.navigateTo(26);
966+
await wait(DEBOUNCE_TIME);
967+
fix.detectChanges();
968+
969+
const firstRowCell = grid.getRowByIndex(26).cells.toArray()[1];
970+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
971+
fix.detectChanges();
972+
973+
expect(grid.navigation.activeNode.row).toBe(26);
974+
expect(grid.navigation.activeNode.column).toBe(1);
975+
976+
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent);
977+
await wait(DEBOUNCE_TIME);
978+
fix.detectChanges();
979+
980+
const lastRowCell = grid.getRowByIndex(27).cells.toArray()[1];
981+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
982+
expect(selectedCell).toBe(lastRowCell);
983+
expect(selectedCell.rowIndex).toBe(27);
984+
});
985+
986+
it('should navigate down from pinned to unpinned row when there are filtered out pinned rows', async() => {
987+
grid.getRowByIndex(5).pin();
988+
grid.getRowByIndex(1).pin();
989+
fix.detectChanges();
990+
991+
grid.filter('ID', 'B', IgxStringFilteringOperand.instance().condition('contains'), false);
992+
fix.detectChanges();
993+
994+
const firstRowCell = grid.getRowByIndex(0).cells.toArray()[1];
995+
UIInteractions.simulateClickAndSelectCellEvent(firstRowCell);
996+
fix.detectChanges();
997+
998+
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent);
999+
await wait(DEBOUNCE_TIME);
1000+
fix.detectChanges();
1001+
1002+
const lastRowCell = grid.getRowByIndex(1).cells.toArray()[1];
1003+
const selectedCell = fix.componentInstance.instance.selectedCells[0];
1004+
expect(selectedCell).toBe(lastRowCell);
1005+
expect(selectedCell.rowIndex).toBe(1);
1006+
});
1007+
});
7771008
});
7781009

7791010
@Component({

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
318318
let top = currGrid.tbody.nativeElement.getBoundingClientRect().top;
319319
while (currGrid.parent) {
320320
currGrid = currGrid.parent;
321-
top = Math.max(top, currGrid.tbody.nativeElement.getBoundingClientRect().top);
321+
const pinnedRowsHeight = currGrid.hasPinnedRecords && currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;
322+
top = Math.max(top, currGrid.tbody.nativeElement.getBoundingClientRect().top + pinnedRowsHeight);
322323
}
323324
return top;
324325
}
@@ -332,7 +333,8 @@ export class IgxHierarchicalGridNavigationService extends IgxGridNavigationServi
332333
let bottom = currGrid.tbody.nativeElement.getBoundingClientRect().bottom;
333334
while (currGrid.parent) {
334335
currGrid = currGrid.parent;
335-
bottom = Math.min(bottom, currGrid.tbody.nativeElement.getBoundingClientRect().bottom);
336+
const pinnedRowsHeight = currGrid.hasPinnedRecords && !currGrid.isRowPinningToTop ? currGrid.pinnedRowHeight : 0;
337+
bottom = Math.min(bottom, currGrid.tbody.nativeElement.getBoundingClientRect().bottom - pinnedRowsHeight);
336338
}
337339
return bottom;
338340
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
| visibleColumns:hasVisibleColumns
100100
| gridRowPinning:id:true:pipeTrigger
101101
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger:true
102-
| gridSort:sortingExpressions:sortStrategy:id:pipeTrigger as pinnedData">
102+
| gridSort:sortingExpressions:sortStrategy:id:pipeTrigger:true as pinnedData">
103103
<div #pinContainer *ngIf='pinnedData.length > 0' class='igx-grid__tr--pinned'
104104
[ngClass]="{ 'igx-grid__tr--pinned-bottom': !isRowPinningToTop, 'igx-grid__tr--pinned-top': isRowPinningToTop }"
105105
[style.bottom.px]=' !isRowPinningToTop ? pinnedBottom : null'>

0 commit comments

Comments
 (0)