Skip to content

Commit 5b81741

Browse files
committed
feat(grid): add new directives for re-templating header sorting indicators
1 parent 45cd818 commit 5b81741

File tree

12 files changed

+183
-56
lines changed

12 files changed

+183
-56
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ All notable changes for each version of this project will be documented in this
1111
- Exposed new input `buttonText` which sets the text that is displayed inside the dropdown button in the toolbar.
1212
- `IgxCombo`
1313
- Added `groupSortingDirection` input, which allows you to set groups sorting order.
14+
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
15+
- Added new directives for re-templating header sorting indicators - `IgxSortHeaderIconDirective`, `igxSortAscendingHeaderIcon` and `igxSortDescendingHeaderIcon`.
1416

1517
### General
1618

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,11 +1603,15 @@
16031603
}
16041604

16051605
.sort-icon {
1606-
width: rem(15px);
1607-
height: rem(15px);
1608-
min-width: rem(15px); /* yeah IE, it really needs to be 15px wide... */
1609-
font-size: rem(15px);
16101606
position: relative;
1607+
display: flex;
1608+
1609+
igx-icon {
1610+
width: rem(15px);
1611+
height: rem(15px);
1612+
min-width: rem(15px); /* yeah IE, it really needs to be 15px wide... */
1613+
font-size: rem(15px);
1614+
}
16111615

16121616
&::after {
16131617
content: attr(data-sortIndex);

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

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ import { CharSeparatedValueData } from '../services/csv/char-separated-value-dat
9393
import { IgxColumnResizingService } from './resizing/resizing.service';
9494
import { IFilteringStrategy } from '../data-operations/filtering-strategy';
9595
import {
96-
IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective,
97-
IgxHeaderExpandIndicatorDirective, IgxHeaderCollapseIndicatorDirective, IgxExcelStyleHeaderIconDirective
96+
IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective, IgxHeaderExpandIndicatorDirective, IgxHeaderCollapseIndicatorDirective,
97+
IgxExcelStyleHeaderIconDirective, IgxSortAscendingHeaderIconDirective, IgxSortDescendingHeaderIconDirective, IgxSortHeaderIconDirective
9898
} from './grid/grid.directives';
9999
import {
100100
GridKeydownTargetType,
@@ -765,8 +765,8 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
765765
* <igx-grid #grid [data]="localData" (rowAdd)="rowAdd($event)" [height]="'305px'" [autoGenerate]="true"></igx-grid>
766766
* ```
767767
*/
768-
@Output()
769-
public rowAdd = new EventEmitter<IGridEditEventArgs>();
768+
@Output()
769+
public rowAdd = new EventEmitter<IGridEditEventArgs>();
770770

771771
/**
772772
* Emitted after column is resized.
@@ -1252,6 +1252,24 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
12521252
@ContentChild(IgxExcelStyleHeaderIconDirective, { read: TemplateRef })
12531253
public excelStyleHeaderIconTemplate: TemplateRef<any> = null;
12541254

1255+
/**
1256+
* The custom template, if any, that should be used when rendering a header sorting indicator when the column is sorted in ascending order.
1257+
*/
1258+
@ContentChild(IgxSortAscendingHeaderIconDirective, { read: TemplateRef })
1259+
public sortAscendingHeaderIconTemplate: TemplateRef<any> = null;
1260+
1261+
/**
1262+
* The custom template, if any, that should be used when rendering a header sorting indicator when the column is sorted in descending order.
1263+
*/
1264+
@ContentChild(IgxSortDescendingHeaderIconDirective, { read: TemplateRef })
1265+
public sortDescendingHeaderIconTemplate: TemplateRef<any> = null;
1266+
1267+
/**
1268+
* The custom template, if any, that should be used when rendering a header sorting indicator when the column is not sorted.
1269+
*/
1270+
@ContentChild(IgxSortHeaderIconDirective, { read: TemplateRef })
1271+
public sortHeaderIconTemplate: TemplateRef<any> = null;
1272+
12551273
/**
12561274
* @hidden
12571275
* @internal
@@ -3442,26 +3460,26 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
34423460
public setUpPaginator() {
34433461
if (this.paginator) {
34443462
this.paginator.pageChange.pipe(takeWhile(() => !!this.paginator), filter(() => !this._init))
3445-
.subscribe((page: number) => {
3446-
this.pageChange.emit(page);
3447-
});
3463+
.subscribe((page: number) => {
3464+
this.pageChange.emit(page);
3465+
});
34483466
this.paginator.pagingDone.pipe(takeWhile(() => !!this.paginator), filter(() => !this._init))
3449-
.subscribe((args: IPageEventArgs) => {
3450-
this.selectionService.clear(true);
3451-
this.pagingDone.emit({ previous: args.previous, current: args.current });
3452-
this.crudService.endEdit(false);
3453-
this.pipeTrigger++;
3454-
this.navigateTo(0);
3455-
this.notifyChanges();
3456-
});
3467+
.subscribe((args: IPageEventArgs) => {
3468+
this.selectionService.clear(true);
3469+
this.pagingDone.emit({ previous: args.previous, current: args.current });
3470+
this.crudService.endEdit(false);
3471+
this.pipeTrigger++;
3472+
this.navigateTo(0);
3473+
this.notifyChanges();
3474+
});
34573475
this.paginator.perPageChange.pipe(takeWhile(() => !!this.paginator), filter(() => !this._init))
3458-
.subscribe((perPage: number) => {
3459-
this.selectionService.clear(true);
3460-
this.perPageChange.emit(perPage);
3461-
this.paginator.page = 0;
3462-
this.crudService.endEdit(false);
3463-
this.notifyChanges();
3464-
});
3476+
.subscribe((perPage: number) => {
3477+
this.selectionService.clear(true);
3478+
this.perPageChange.emit(perPage);
3479+
this.paginator.page = 0;
3480+
this.crudService.endEdit(false);
3481+
this.notifyChanges();
3482+
});
34653483
} else {
34663484
this.markForCheck();
34673485
}
@@ -3932,7 +3950,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
39323950
* ```
39333951
*/
39343952
public get columnsCollection(): IgxColumnComponent[] {
3935-
return this._rendered ? this._columns : [];
3953+
return this._rendered ? this._columns : [];
39363954
}
39373955

39383956
/**
@@ -4240,7 +4258,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
42404258
* @param val
42414259
*/
42424260
// eslint-disable-next-line @typescript-eslint/member-ordering
4243-
@DeprecateMethod('Use the corresponding method exposed by the `igx-paginator`.')
4261+
@DeprecateMethod('Use the corresponding method exposed by the `igx-paginator`.')
42444262
public paginate(val: number): void {
42454263
this.paginator?.paginate(val);
42464264
}
@@ -5483,7 +5501,7 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
54835501
const selectedColumns = this.gridAPI.grid.selectedColumns();
54845502
const columnData = this.getSelectedColumnsData(this.clipboardOptions.copyFormatters, this.clipboardOptions.copyHeaders);
54855503
let selectedData;
5486-
if (event.type === 'copy'){
5504+
if (event.type === 'copy') {
54875505
selectedData = this.getSelectedData(this.clipboardOptions.copyFormatters, this.clipboardOptions.copyHeaders);
54885506
};
54895507

@@ -5982,8 +6000,8 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
59826000

59836001
protected subscribeToTransactions(): void {
59846002
this.transactionChange$.next();
5985-
this.transactions.onStateUpdate.pipe(takeUntil(merge(this.destroy$,this.transactionChange$)))
5986-
.subscribe(this.transactionStatusUpdate.bind(this));
6003+
this.transactions.onStateUpdate.pipe(takeUntil(merge(this.destroy$, this.transactionChange$)))
6004+
.subscribe(this.transactionStatusUpdate.bind(this));
59876005
}
59886006

59896007
protected transactionStatusUpdate(event: StateUpdateEvent) {
@@ -6743,10 +6761,10 @@ export abstract class IgxGridBaseDirective extends DisplayDensityBase implements
67436761
let selectionMap;
67446762
if (this.nativeElement.tagName.toLowerCase() === 'igx-hierarchical-grid' && selectionCollection.size > 0) {
67456763
selectionMap = isRemote ? Array.from(selectionCollection) :
6746-
Array.from(selectionCollection).filter((tuple) => tuple[0] < source.length);
6747-
}else {
6764+
Array.from(selectionCollection).filter((tuple) => tuple[0] < source.length);
6765+
} else {
67486766
selectionMap = isRemote ? Array.from(this.selectionService.selection) :
6749-
Array.from(this.selectionService.selection).filter((tuple) => tuple[0] < source.length);
6767+
Array.from(this.selectionService.selection).filter((tuple) => tuple[0] < source.length);
67506768
}
67516769

67526770
if (this.cellSelection === GridSelectionMode.single && activeEl) {

projects/igniteui-angular/src/lib/grids/grid-common.module.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { IgxGridFilteringModule } from './filtering/base/filtering.module';
2929
import { IgxRowDirective } from './row.directive';
3030
import {
3131
IgxExcelStyleHeaderIconDirective,
32+
IgxSortAscendingHeaderIconDirective,
33+
IgxSortDescendingHeaderIconDirective,
34+
IgxSortHeaderIconDirective,
3235
IgxGroupAreaDropDirective,
3336
IgxHeaderCollapseIndicatorDirective,
3437
IgxHeaderExpandIndicatorDirective,
@@ -58,6 +61,9 @@ import { IgxGroupByMetaPipe } from './grouping/group-by-area.directive';
5861
IgxHeaderExpandIndicatorDirective,
5962
IgxHeaderCollapseIndicatorDirective,
6063
IgxExcelStyleHeaderIconDirective,
64+
IgxSortAscendingHeaderIconDirective,
65+
IgxSortDescendingHeaderIconDirective,
66+
IgxSortHeaderIconDirective,
6167
IgxGroupAreaDropDirective,
6268
IgxGroupByMetaPipe
6369
],
@@ -93,6 +99,9 @@ import { IgxGroupByMetaPipe } from './grouping/group-by-area.directive';
9399
IgxHeaderExpandIndicatorDirective,
94100
IgxHeaderCollapseIndicatorDirective,
95101
IgxExcelStyleHeaderIconDirective,
102+
IgxSortAscendingHeaderIconDirective,
103+
IgxSortDescendingHeaderIconDirective,
104+
IgxSortHeaderIconDirective,
96105
IgxGroupAreaDropDirective,
97106
IgxGroupByMetaPipe
98107
],

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ export class IgxHeaderCollapseIndicatorDirective {
7272
export class IgxExcelStyleHeaderIconDirective {
7373
}
7474

75+
/**
76+
* @hidden
77+
*/
78+
@Directive({
79+
selector: '[igxSortHeaderIcon]'
80+
})
81+
export class IgxSortHeaderIconDirective {
82+
}
83+
84+
/**
85+
* @hidden
86+
*/
87+
@Directive({
88+
selector: '[igxSortAscendingHeaderIcon]'
89+
})
90+
export class IgxSortAscendingHeaderIconDirective {
91+
}
92+
93+
/**
94+
* @hidden
95+
*/
96+
@Directive({
97+
selector: '[igxSortDescendingHeaderIcon]'
98+
})
99+
export class IgxSortDescendingHeaderIconDirective {
100+
}
101+
75102
/**
76103
* @hidden
77104
*/

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,5 +549,26 @@ describe('IgxGrid - Grid Sorting #grid', () => {
549549
expect(grid.sorting.emit).toHaveBeenCalledTimes(2);
550550
expect(grid.sortingDone.emit).toHaveBeenCalledTimes(2);
551551
}));
552+
553+
it('Should allow setting custom templates for header sorting none/ascending/descending icons.', fakeAsync(/** Filtering showHideArrowButtons RAF */() => {
554+
fixture = TestBed.createComponent(SortByParityComponent);
555+
fixture.detectChanges();
556+
grid = fixture.componentInstance.grid;
557+
const fieldName = 'Name';
558+
const header = GridFunctions.getColumnHeader(fieldName, fixture, grid);
559+
let icon = GridFunctions.getHeaderSortIcon(header);
560+
561+
expect(icon.nativeElement.textContent.toLowerCase().trim()).toBe('unfold_more');
562+
563+
grid.sort({ fieldName, dir: SortingDirection.Asc, ignoreCase: false });
564+
fixture.detectChanges();
565+
icon = GridFunctions.getHeaderSortIcon(header);
566+
expect(icon.nativeElement.textContent.toLowerCase().trim()).toBe('expand_less');
567+
568+
grid.sort({ fieldName, dir: SortingDirection.Desc, ignoreCase: false });
569+
fixture.detectChanges();
570+
icon = GridFunctions.getHeaderSortIcon(header);
571+
expect(icon.nativeElement.textContent.toLowerCase().trim()).toBe('expand_more');
572+
}));
552573
});
553574
});

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,28 @@
66
<igx-icon>more_vert</igx-icon>
77
</ng-template>
88

9+
<ng-template #defaultSortHeaderIconTemplate>
10+
<igx-icon>{{ sortDirection < 2 ? 'arrow_upward' : 'arrow_downward' }}</igx-icon>
11+
</ng-template>
12+
913
<span class="igx-grid-th__title">
10-
<ng-container *ngTemplateOutlet="column.headerTemplate ? column.headerTemplate : defaultColumn; context: { $implicit: column, column: column}">
14+
<ng-container
15+
*ngTemplateOutlet="column.headerTemplate ? column.headerTemplate : defaultColumn; context: { $implicit: column, column: column}">
1116
</ng-container>
1217
</span>
1318
<ng-container *ngIf="!column.columnGroup">
1419
<div class="igx-grid-th__icons">
1520
<ng-container *ngIf="column.sortable">
16-
<igx-icon class="sort-icon"
17-
[attr.draggable]="false"
18-
[attr.data-sortIndex]="column.field | sortingIndex:grid.sortingExpressions"
19-
(click)="onSortingIconClick($event)">
20-
{{ sortDirection < 2 ? 'arrow_upward' : 'arrow_downward' }}
21-
</igx-icon>
21+
<div class="sort-icon" [attr.data-sortIndex]="column.field | sortingIndex:grid.sortingExpressions"
22+
[attr.draggable]="false" (click)="onSortingIconClick($event)" (pointerdown)="$event.stopPropagation()">
23+
<ng-container *ngTemplateOutlet="sortIconTemplate; context: { $implicit: this }"></ng-container>
24+
</div>
2225
</ng-container>
2326
<ng-container *ngIf="grid.allowFiltering && column.filterable && grid.filterMode === 'excelStyleFilter'">
24-
<div [ngClass]="filterIconClassName" (click)="onFilteringIconClick($event)" (pointerdown)="$event.stopPropagation()">
27+
<div [ngClass]="filterIconClassName" (click)="onFilteringIconClick($event)"
28+
(pointerdown)="$event.stopPropagation()">
2529
<ng-container *ngTemplateOutlet="esfIconTemplate; context: { $implicit: this }"></ng-container>
2630
</div>
2731
</ng-container>
2832
</div>
29-
</ng-container>
33+
</ng-container>

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export class IgxGridHeaderComponent implements DoCheck, OnDestroy {
4444
@ViewChild('defaultESFHeaderIconTemplate', { read: TemplateRef, static: true })
4545
protected defaultESFHeaderIconTemplate: TemplateRef<any>;
4646

47+
/**
48+
* @hidden
49+
*/
50+
@ViewChild('defaultSortHeaderIconTemplate', { read: TemplateRef, static: true })
51+
protected defaultSortHeaderIconTemplate;
52+
4753
/**
4854
* Returns the `aria-selected` of the header.
4955
*/
@@ -131,6 +137,21 @@ export class IgxGridHeaderComponent implements DoCheck, OnDestroy {
131137
return this.grid.excelStyleHeaderIconTemplate || this.defaultESFHeaderIconTemplate;
132138
}
133139

140+
/**
141+
* @hidden
142+
*/
143+
public get sortIconTemplate() {
144+
if (this.sortDirection === SortingDirection.None && this.grid.sortHeaderIconTemplate) {
145+
return this.grid.sortHeaderIconTemplate;
146+
} else if (this.sortDirection === SortingDirection.Asc && this.grid.sortAscendingHeaderIconTemplate) {
147+
return this.grid.sortAscendingHeaderIconTemplate;
148+
} else if (this.sortDirection === SortingDirection.Desc && this.grid.sortDescendingHeaderIconTemplate) {
149+
return this.grid.sortDescendingHeaderIconTemplate;
150+
} else {
151+
return this.defaultSortHeaderIconTemplate;
152+
}
153+
}
154+
134155
public get sorted() {
135156
return this.sortDirection !== SortingDirection.None;
136157
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti
442442
this.headerCollapseIndicatorTemplate = this.rootGrid.headerCollapseIndicatorTemplate;
443443
this.headerExpandIndicatorTemplate = this.rootGrid.headerExpandIndicatorTemplate;
444444
this.excelStyleHeaderIconTemplate = this.rootGrid.excelStyleHeaderIconTemplate;
445+
this.sortAscendingHeaderIconTemplate = this.rootGrid.sortAscendingHeaderIconTemplate;
446+
this.sortDescendingHeaderIconTemplate = this.rootGrid.sortDescendingHeaderIconTemplate;
447+
this.sortHeaderIconTemplate = this.rootGrid.sortHeaderIconTemplate;
445448
this.hasChildrenKey = this.parentIsland ?
446449
this.parentIsland.hasChildrenKey || this.rootGrid.hasChildrenKey :
447450
this.rootGrid.hasChildrenKey;

projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,7 +1819,7 @@ export class GridFunctions {
18191819

18201820
public static getColumnGroupHeaderCell(columnField: string, fix: ComponentFixture<any>) {
18211821
const headerTitle = fix.debugElement.queryAll(By.css(GROUP_HEADER_CLASS))
1822-
.find(header => header.nativeElement.title === columnField);
1822+
.find(header => header.nativeElement.title === columnField);
18231823
return headerTitle.parent;
18241824
}
18251825

@@ -1867,20 +1867,20 @@ export class GridFunctions {
18671867
}
18681868

18691869
public static getHeaderSortIcon(header: DebugElement): DebugElement {
1870-
return header.query(By.css(SORT_ICON_CLASS));
1870+
return header.query(By.css(SORT_ICON_CLASS))?.query(By.css('igx-icon'));
18711871
}
18721872

18731873
public static getHeaderFilterIcon(header: DebugElement): DebugElement {
18741874
return header.query(By.css(FILTER_ICON_CLASS));
18751875
}
18761876

18771877
public static clickHeaderSortIcon(header: DebugElement) {
1878-
const sortIcon = header.query(By.css(SORT_ICON_CLASS));
1878+
const sortIcon = this.getHeaderSortIcon(header);
18791879
sortIcon.triggerEventHandler('click', new Event('click'));
18801880
}
18811881

18821882
public static verifyHeaderSortIndicator(header: DebugElement, sortedAsc = true, sortedDesc = false, sortable = true) {
1883-
const sortIcon = header.query(By.css(SORT_ICON_CLASS));
1883+
const sortIcon = this.getHeaderSortIcon(header);
18841884
if (sortable) {
18851885
const sortIconText = sortedDesc ? SORTING_ICON_DESC_CONTENT : SORTING_ICON_ASC_CONTENT;
18861886
expect(sortIcon.nativeElement.textContent.trim()).toEqual(sortIconText);

0 commit comments

Comments
 (0)