Skip to content

Commit e0af43d

Browse files
jackofdiamond5Lipata
authored andcommitted
feat(grid): row selection templating #4998
1 parent 117b450 commit e0af43d

22 files changed

+1123
-75
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ For more information about the theming please read our [documentation](https://w
3232
- `uniqueColumnValuesStrategy` input is added. This property provides a callback for loading unique column values on demand. If this property is provided, the unique values it generates will be used by the Excel Style Filtering (instead of using the unique values from the data that is bound to the grid).
3333
- `igxExcelStyleLoading` directive is added, which can be used to provide a custom loading template for the Excel Style Filtering. If this property is not provided, a default loading template will be used instead.
3434
- introduced new properties `cellSelection` and `rowSelection` which accept GridSelection mode enumeration. Grid selection mode could be none, single or multiple. Also `hideRowSelectors` property is added, which allows you to show and hide row selectors when row selection is enabled.
35+
- introduced functionality for templating row and header selectors - [spec](https://github.com/IgniteUI/igniteui-angular/wiki/Row-Selection-Templating-(Grid-feature))
36+
```html
37+
<igx-grid [data]="data", [rowSelection]="'multiple'" primaryKey="ID">
38+
<igx-column field="Name"></igx-column>
39+
<igx-column field="Age"></igx-column>
40+
41+
<ng-template igxHeadSelector let-headSelector>
42+
<igx-icon>done_all</igx-icon>
43+
</ng-template>
44+
<ng-template igxRowSelector let-rowContext>
45+
<igx-switch [checked]="rowContext.selected"></igx-switch>
46+
</ng-template>
47+
</igx-grid>
48+
```
3549
- `IgxHierarchicalGrid`
3650
- Row Islands now emit child grid events with an additional argument - `owner`, which holds reference to the related child grid component instance.
3751
- `IgxDrag`

projects/igniteui-angular/src/lib/core/grid-selection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,12 @@ export class IgxGridSelectionService {
635635
return this.rowSelection.size > 0 && filteredData && !this.areAllRowSelected();
636636
}
637637

638+
public get filteredSelectedRowIds(): any[] {
639+
return this.isFilteringApplied() ?
640+
this.getRowIDs(this.allData).filter(rowID => this.isRowSelected(rowID)) :
641+
this.getSelectedRows().filter(rowID => !this.isRowDeleted(rowID));
642+
}
643+
638644
public emitRowSelectionEvent(newSelection, added, removed, event?): boolean {
639645
const currSelection = this.getSelectedRows();
640646
if (this.areEqualCollections(currSelection, newSelection)) { return; }

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

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
VerticalAlignment,
4545
IgxOverlayService
4646
} from '../services/index';
47-
import { IgxCheckboxComponent } from './../checkbox/checkbox.component';
4847
import { GridBaseAPIService } from './api.service';
4948
import { IgxGridCellComponent } from './cell.component';
5049
import { IColumnVisibilityChangedEventArgs } from './column-hiding-item.directive';
@@ -89,8 +88,9 @@ import {
8988
import { IgxGridColumnResizerComponent } from './grid-column-resizer.component';
9089
import { IgxGridFilteringRowComponent } from './filtering/grid-filtering-row.component';
9190
import { IgxDragDirective } from '../directives/drag-drop/drag-drop.directive';
92-
import { DeprecateProperty } from '../core/deprecateDecorators';
9391
import { CharSeparatedValueData } from '../services/csv/char-separated-value-data';
92+
import { IgxHeadSelectorDirective, IgxRowSelectorDirective } from './igx-row-selectors.module';
93+
import { DeprecateProperty } from '../core/deprecateDecorators';
9494

9595
const MINIMUM_COLUMN_WIDTH = 136;
9696
const FILTER_ROW_HEIGHT = 50;
@@ -259,6 +259,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
259259
private _locale = null;
260260
public _destroyed = false;
261261
private overlayIDs = [];
262+
262263
private _hostWidth;
263264
/**
264265
* An accessor that sets the resource strings.
@@ -1707,9 +1708,51 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
17071708
return this.toolbarCustomContentTemplates.first;
17081709
}
17091710

1711+
/**
1712+
* @hidden
1713+
* @internal
1714+
*/
17101715
@ContentChildren(IgxGridToolbarCustomContentDirective, { read: IgxGridToolbarCustomContentDirective, descendants: false })
17111716
public toolbarCustomContentTemplates: QueryList<IgxGridToolbarCustomContentDirective>;
17121717

1718+
/**
1719+
* @hidden
1720+
* @internal
1721+
*/
1722+
public get headSelectorTemplate(): TemplateRef<IgxHeadSelectorDirective> {
1723+
if (this.headSelectorsTemplates && this.headSelectorsTemplates.first) {
1724+
return this.headSelectorsTemplates.first.templateRef;
1725+
}
1726+
1727+
return null;
1728+
}
1729+
1730+
/**
1731+
* @hidden
1732+
* @internal
1733+
*/
1734+
@ContentChildren(IgxHeadSelectorDirective, { read: IgxHeadSelectorDirective, descendants: false })
1735+
public headSelectorsTemplates: QueryList<IgxHeadSelectorDirective>;
1736+
1737+
/**
1738+
* @hidden
1739+
* @internal
1740+
*/
1741+
public get rowSelectorTemplate(): TemplateRef<IgxRowSelectorDirective> {
1742+
if (this.rowSelectorsTemplates && this.rowSelectorsTemplates.first) {
1743+
return this.rowSelectorsTemplates.first.templateRef;
1744+
}
1745+
1746+
return null;
1747+
}
1748+
1749+
/**
1750+
* @hidden
1751+
* @internal
1752+
*/
1753+
@ContentChildren(IgxRowSelectorDirective, { read: IgxRowSelectorDirective, descendants: false })
1754+
public rowSelectorsTemplates: QueryList<IgxRowSelectorDirective>;
1755+
17131756
/**
17141757
* @hidden
17151758
*/
@@ -1743,8 +1786,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
17431786
/**
17441787
* @hidden
17451788
*/
1746-
@ViewChild('headerCheckboxContainer', { static: false })
1747-
public headerCheckboxContainer: ElementRef;
1789+
@ViewChild('headerSelectorContainer', { static: false })
1790+
public headerSelectorContainer: ElementRef;
17481791

17491792
/**
17501793
* @hidden
@@ -1758,12 +1801,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
17581801
@ViewChild('headerGroupContainer', { static: false })
17591802
public headerGroupContainer: ElementRef;
17601803

1761-
/**
1762-
* @hidden
1763-
*/
1764-
@ViewChild('headerCheckbox', { read: IgxCheckboxComponent, static: false })
1765-
public headerCheckbox: IgxCheckboxComponent;
1766-
17671804
/**
17681805
* @hidden
17691806
*/
@@ -1841,6 +1878,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
18411878
*/
18421879
@ViewChild('defaultRowEditTemplate', { read: TemplateRef, static: true })
18431880
private defaultRowEditTemplate: TemplateRef<any>;
1881+
18441882
/**
18451883
* @hidden
18461884
*/
@@ -3337,10 +3375,18 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
33373375
return totalWidth;
33383376
}
33393377

3340-
get showRowCheckboxes(): boolean {
3378+
/**
3379+
* @hidden
3380+
* @internal
3381+
*/
3382+
get showRowSelectors(): boolean {
33413383
return this.isRowSelectable && this.hasVisibleColumns && !this.hideRowSelectors;
33423384
}
33433385

3386+
/**
3387+
* @hidden
3388+
* @internal
3389+
*/
33443390
get showDragIcons(): boolean {
33453391
return this.rowDraggable && this.columns.length > this.hiddenColumnsCount;
33463392
}
@@ -4167,7 +4213,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
41674213
const pagingHeight = this.getPagingHeight();
41684214
const groupAreaHeight = this.getGroupAreaHeight();
41694215
const renderedHeight = toolbarHeight + this.theadRow.nativeElement.offsetHeight +
4170-
footerHeight + pagingHeight + groupAreaHeight +
4216+
footerHeight + pagingHeight + groupAreaHeight +
41714217
this.scr.nativeElement.clientHeight;
41724218

41734219
const computed = this.document.defaultView.getComputedStyle(this.nativeElement).getPropertyValue('height');
@@ -4476,7 +4522,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
44764522
let width = 0;
44774523

44784524
if (this.isRowSelectable) {
4479-
width += this.headerCheckboxContainer ? this.headerCheckboxContainer.nativeElement.getBoundingClientRect().width : 0;
4525+
width += this.headerSelectorContainer ? this.headerSelectorContainer.nativeElement.getBoundingClientRect().width : 0;
44804526
}
44814527
if (this.rowDraggable) {
44824528
width += this.headerDragContainer ? this.headerDragContainer.nativeElement.getBoundingClientRect().width : 0;
@@ -4685,10 +4731,24 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
46854731
/**
46864732
* @hidden
46874733
*/
4688-
get headerCheckboxAriaLabel() {
4689-
return this._filteringExpressionsTree.filteringOperands.length > 0 ?
4690-
this.headerCheckbox && this.selectionService.areAllRowSelected() ? 'Deselect all filtered' : 'Select all filtered' :
4691-
this.headerCheckbox && this.selectionService.areAllRowSelected() ? 'Deselect all' : 'Select all';
4734+
get headSelectorBaseAriaLabel() {
4735+
if (this._filteringExpressionsTree.filteringOperands.length > 0) {
4736+
return this.selectionService.areAllRowSelected() ? 'Deselect all filtered' : 'Select all filtered';
4737+
}
4738+
4739+
return this.selectionService.areAllRowSelected() ? 'Deselect all' : 'Select all';
4740+
}
4741+
4742+
/**
4743+
* @hidden
4744+
* @internal
4745+
*/
4746+
public get totalRowsCountAfterFilter() {
4747+
if (this.data) {
4748+
return this.selectionService.allData.length;
4749+
}
4750+
4751+
return 0;
46924752
}
46934753

46944754
/**
@@ -4875,7 +4935,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
48754935
if (col) {
48764936
const key = headers ? col.header || col.field : col.field;
48774937
record[key] = formatters && col.formatter ? col.formatter(source[row][col.field])
4878-
: source[row][col.field];
4938+
: source[row][col.field];
48794939
}
48804940
});
48814941
}
@@ -4890,15 +4950,15 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
48904950
protected getSelectableColumnsAt(index) {
48914951
if (this.hasColumnLayouts) {
48924952
const visibleLayoutColumns = this.visibleColumns
4893-
.filter(col => col.columnLayout)
4894-
.sort((a, b) => a.visibleIndex - b.visibleIndex);
4953+
.filter(col => col.columnLayout)
4954+
.sort((a, b) => a.visibleIndex - b.visibleIndex);
48954955
const colLayout = visibleLayoutColumns[index];
48964956
return colLayout ? colLayout.children.toArray() : [];
48974957
} else {
48984958
const visibleColumns = this.visibleColumns
4899-
.filter(col => !col.columnGroup)
4900-
.sort((a, b) => a.visibleIndex - b.visibleIndex);
4901-
return [ visibleColumns[index] ];
4959+
.filter(col => !col.columnGroup)
4960+
.sort((a, b) => a.visibleIndex - b.visibleIndex);
4961+
return [visibleColumns[index]];
49024962
}
49034963
}
49044964

@@ -4913,7 +4973,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
49134973
return this.extractDataFromSelection(source, formatters, headers);
49144974
}
49154975

4916-
49174976
/**
49184977
* @hidden
49194978
*/
@@ -4944,7 +5003,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
49445003
this.onGridCopy.emit(ev);
49455004

49465005
if (ev.cancel) {
4947-
return;
5006+
return;
49485007
}
49495008

49505009
const transformer = new CharSeparatedValueData(ev.data, this.clipboardOptions.separator);

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { IgxGridExcelStyleFilteringModule } from './filtering/excel-style/grid.e
6767
import { IgxGridDragSelectDirective } from './drag-select.directive';
6868
import { IgxGridColumnResizerComponent } from './grid-column-resizer.component';
6969
import { IgxRowDragModule } from './row-drag.directive';
70+
import { IgxRowSelectorsModule } from './igx-row-selectors.module';
7071
/**
7172
* @hidden
7273
*/
@@ -164,7 +165,8 @@ import { IgxRowDragModule } from './row-drag.directive';
164165
IgxFilterCellTemplateDirective,
165166
IgxRowDragModule,
166167
IgxPaginatorModule,
167-
IgxGridFooterComponent
168+
IgxGridFooterComponent,
169+
IgxRowSelectorsModule
168170
],
169171
imports: [
170172
CommonModule,
@@ -193,7 +195,8 @@ import { IgxRowDragModule } from './row-drag.directive';
193195
IgxGridPipesModule,
194196
IgxGridExcelStyleFilteringModule,
195197
IgxRowDragModule,
196-
IgxPaginatorModule
198+
IgxPaginatorModule,
199+
IgxRowSelectorsModule
197200
],
198201
providers: [
199202
IgxGridSelectionService,

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

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {
1212
SelectionWithScrollsComponent,
1313
SingleRowSelectionComponent,
1414
RowSelectionWithoutPrimaryKeyComponent,
15-
SelectionWithTransactionsComponent
15+
SelectionWithTransactionsComponent,
16+
GridCustomSelectorsComponent
1617
} from '../../test-utils/grid-samples.spec';
17-
import { IgxHierarchicalGridModule } from '../hierarchical-grid/hierarchical-grid.module';
1818
import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec';
1919
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
20+
import { IgxRowSelectorsModule } from '../igx-row-selectors.module';
2021

2122
const DEBOUNCETIME = 30;
2223

@@ -31,12 +32,13 @@ describe('IgxGrid - Row Selection #grid', () => {
3132
SelectionWithScrollsComponent,
3233
RowSelectionWithoutPrimaryKeyComponent,
3334
SingleRowSelectionComponent,
34-
SelectionWithTransactionsComponent
35+
SelectionWithTransactionsComponent,
36+
GridCustomSelectorsComponent,
3537
],
3638
imports: [
3739
NoopAnimationsModule,
3840
IgxGridModule,
39-
IgxHierarchicalGridModule
41+
IgxRowSelectorsModule
4042
]
4143
})
4244
.compileComponents();
@@ -1871,4 +1873,62 @@ describe('IgxGrid - Row Selection #grid', () => {
18711873
GridSelectionFunctions.verifyRowSelected(addedRow);
18721874
}));
18731875
});
1876+
1877+
describe('Custom selectors', () => {
1878+
let fix;
1879+
let grid;
1880+
1881+
beforeEach(fakeAsync(() => {
1882+
fix = TestBed.createComponent(GridCustomSelectorsComponent);
1883+
fix.detectChanges();
1884+
grid = fix.componentInstance.grid;
1885+
grid.rowSelection = GridSelectionMode.multiple;
1886+
}));
1887+
1888+
it('Should have the correct properties in the custom row selector template', () => {
1889+
const firstRow = grid.getRowByIndex(0);
1890+
const firstCheckbox = firstRow.nativeElement.querySelector('.igx-checkbox__composite');
1891+
const context = { index: 0, rowID: 'ALFKI', selected: false };
1892+
const contextUnselect = { index: 0, rowID: 'ALFKI', selected: true };
1893+
spyOn(fix.componentInstance, 'onRowCheckboxClick').and.callThrough();
1894+
firstCheckbox.click();
1895+
fix.detectChanges();
1896+
1897+
expect(fix.componentInstance.onRowCheckboxClick).toHaveBeenCalledTimes(1);
1898+
expect(fix.componentInstance.onRowCheckboxClick).toHaveBeenCalledWith(new MouseEvent('click'), context);
1899+
1900+
// Verify correct properties when unselecting a row
1901+
firstCheckbox.click();
1902+
fix.detectChanges();
1903+
1904+
expect(fix.componentInstance.onRowCheckboxClick).toHaveBeenCalledTimes(2);
1905+
expect(fix.componentInstance.onRowCheckboxClick).toHaveBeenCalledWith(new MouseEvent('click'), contextUnselect);
1906+
});
1907+
1908+
it('Should have the correct properties in the custom row selector header template', () => {
1909+
const context = { selectedCount: 0, totalCount: 27 };
1910+
const contextUnselect = { selectedCount: 27, totalCount: 27 };
1911+
const headerCheckbox = fix.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__composite');
1912+
spyOn(fix.componentInstance, 'onHeaderCheckboxClick').and.callThrough();
1913+
headerCheckbox.click();
1914+
fix.detectChanges();
1915+
1916+
expect(fix.componentInstance.onHeaderCheckboxClick).toHaveBeenCalledTimes(1);
1917+
expect(fix.componentInstance.onHeaderCheckboxClick).toHaveBeenCalledWith(new MouseEvent('click'), context);
1918+
1919+
headerCheckbox.click();
1920+
fix.detectChanges();
1921+
1922+
expect(fix.componentInstance.onHeaderCheckboxClick).toHaveBeenCalledTimes(2);
1923+
expect(fix.componentInstance.onHeaderCheckboxClick).toHaveBeenCalledWith(new MouseEvent('click'), contextUnselect);
1924+
});
1925+
1926+
it('Should have correct indices on all pages', () => {
1927+
grid.nextPage();
1928+
fix.detectChanges();
1929+
1930+
const firstRootRow = grid.getRowByIndex(0);
1931+
expect(firstRootRow.nativeElement.querySelector('.rowNumber').textContent).toEqual('30');
1932+
});
1933+
});
18741934
});

0 commit comments

Comments
 (0)