Skip to content

Commit b7e93a5

Browse files
feat: table expand all rows feature
1 parent 6f06496 commit b7e93a5

File tree

7 files changed

+126
-3
lines changed

7 files changed

+126
-3
lines changed

src/i18n/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export default {
8989
"CHECKBOX_HEADER": "Select all rows",
9090
"CHECKBOX_ROW": "Select {{value}}",
9191
"EXPAND_BUTTON": "Expand row",
92+
"EXPAND_ALL_BUTTON": "Expand all rows",
9293
"SORT_DESCENDING": "Sort rows by this header in descending order",
9394
"SORT_ASCENDING": "Sort rows by this header in ascending order",
9495
"ROW": "row"
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,42 @@
11
import {
22
Component,
3-
HostBinding
3+
EventEmitter,
4+
HostBinding,
5+
Input,
6+
Output
47
} from "@angular/core";
8+
import { I18n } from "carbon-components-angular/i18n";
9+
import { Observable } from "rxjs";
510

611
@Component({
712
// tslint:disable-next-line: component-selector
813
selector: "[cdsTableHeadExpand], [ibmTableHeadExpand]",
914
template: `
10-
<ng-content></ng-content>
15+
<button
16+
class="cds--table-expand__button"
17+
[attr.aria-label]="getAriaLabel() | async"
18+
(click)="change.emit(!allRowsExpanded)"
19+
>
20+
<svg cdsIcon="chevron--right" size="16" class="cds--table-expand__svg"></svg>
21+
</button>
1122
`
1223
})
1324
export class TableHeadExpand {
1425
@HostBinding("class.cds--table-expand") hostClass = true;
26+
27+
@Input() allRowsExpanded = false;
28+
29+
@Output() change = new EventEmitter<boolean>();
30+
31+
@HostBinding("attr.data-previous-value") get previousValue() {
32+
return this.allRowsExpanded ? "collapsed" : null;
33+
}
34+
35+
protected _ariaLabel = this.i18n.getOverridable("TABLE.EXPAND_ALL_BUTTON");
36+
37+
constructor(protected i18n: I18n) { }
38+
39+
getAriaLabel(): Observable<string> {
40+
return this._ariaLabel.subject;
41+
}
1542
}

src/table/head/table-head.component.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import { TableRowSize } from "../table.types";
3232
*ngIf="model.hasExpandableRows()"
3333
scope="col"
3434
[ngClass]="{'cds--table-expand-v2': stickyHeader}"
35-
[id]="model.getId('expand')">
35+
[id]="model.getId('expand')"
36+
[allRowsExpanded]="model.expandableRowsCount() === model.expandedRowsCount()"
37+
(change)="onExpandAllRowsChange($event)">
3638
</th>
3739
<th
3840
*ngIf="!skeleton && showSelectionColumn && enableSingleSelect"
@@ -160,6 +162,18 @@ export class TableHead implements AfterViewInit {
160162
* @param model
161163
*/
162164
@Output() deselectAll = new EventEmitter<TableModel>();
165+
/**
166+
* Emits if all rows are expanded.
167+
*
168+
* @param model
169+
*/
170+
@Output() expandAllRows = new EventEmitter<TableModel>();
171+
/**
172+
* Emits if all rows are collapsed.
173+
*
174+
* @param model
175+
*/
176+
@Output() collapseAllRows = new EventEmitter<TableModel>();
163177

164178
public scrollbarWidth = 0;
165179

@@ -184,6 +198,14 @@ export class TableHead implements AfterViewInit {
184198
}
185199
}
186200

201+
onExpandAllRowsChange(expand: boolean) {
202+
if (expand) {
203+
this.expandAllRows.emit(this.model);
204+
} else {
205+
this.collapseAllRows.emit(this.model);
206+
}
207+
}
208+
187209
getCheckboxHeaderLabel(): Observable<string> {
188210
return this._checkboxHeaderLabel.subject;
189211
}

src/table/stories/app-expansion-table.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class CustomHeaderItem extends TableHeaderItem {
4747
(rowClick)="onRowClick($event)"
4848
[isDataGrid]="isDataGrid">
4949
</cds-table>
50+
51+
<br>
52+
53+
<button cdsButton="primary" size="sm" (click)="model.expandAllRows(true)">Expand all rows</button>
54+
55+
<button cdsButton="secondary" size="sm" (click)="model.expandAllRows(false)">Collapse all rows</button>
5056
`
5157
})
5258
export class ExpansionTableStory implements AfterViewInit {

src/table/table-model.class.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,4 +760,34 @@ describe("Table", () => {
760760
expect(tableModel.header[1].data).toEqual("h2");
761761
expect(tableModel.header[2].data).toEqual("h3");
762762
});
763+
764+
it("should expand and collapse all rows", () => {
765+
let tableModel = new TableModel();
766+
767+
spyOn(tableModel.rowsExpandedAllChange, 'emit');
768+
spyOn(tableModel.rowsCollapsedAllChange, 'emit');
769+
770+
tableModel.header = [
771+
new TableHeaderItem({data: "h1"}), new TableHeaderItem({data: "h2"})
772+
];
773+
tableModel.data = [
774+
[new TableItem({data: "A", expandedData: "EX1"}), new TableItem({data: "B"})],
775+
[new TableItem({data: "C"}), new TableItem({data: "D"})],
776+
[new TableItem({data: "E", expandedData: "EX2"}), new TableItem({data: "F"})],
777+
[new TableItem({data: "G"}), new TableItem({data: "H"})],
778+
];
779+
780+
expect(tableModel.expandableRowsCount()).toBe(2);
781+
expect(tableModel.expandedRowsCount()).toBe(0);
782+
783+
tableModel.expandAllRows(true);
784+
expect(tableModel.rowsExpandedAllChange.emit).toHaveBeenCalledWith();
785+
expect(tableModel.expandedRowsCount()).toBe(2);
786+
expect(tableModel.rowsExpanded).toEqual([true, false, true, false])
787+
788+
tableModel.expandAllRows(false);
789+
expect(tableModel.rowsCollapsedAllChange.emit).toHaveBeenCalledWith();
790+
expect(tableModel.expandedRowsCount()).toBe(0);
791+
expect(tableModel.rowsExpanded).toEqual([false, false, false, false])
792+
});
763793
});

src/table/table-model.class.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export class TableModel implements PaginationModel {
6969
dataChange = new EventEmitter();
7070
rowsSelectedChange = new EventEmitter<number>();
7171
rowsExpandedChange = new EventEmitter<number>();
72+
rowsExpandedAllChange = new EventEmitter();
73+
rowsCollapsedAllChange = new EventEmitter();
7274
/**
7375
* Gets emitted when `selectAll` is called. Emits false if all rows are deselected and true if
7476
* all rows are selected.
@@ -413,6 +415,18 @@ export class TableModel implements PaginationModel {
413415
return this.data.some(data => data.some(d => d && d.expandedData)); // checking for some in 2D array
414416
}
415417

418+
/**
419+
* Number of rows that can be expanded.
420+
*
421+
* @returns number
422+
*/
423+
expandableRowsCount() {
424+
return this.data.reduce((counter, _, index) => {
425+
counter = (this.isRowExpandable(index)) ? counter + 1 : counter;
426+
return counter;
427+
}, 0);
428+
}
429+
416430
isRowExpandable(index: number) {
417431
return this.data[index].some(d => d && d.expandedData);
418432
}
@@ -705,6 +719,27 @@ export class TableModel implements PaginationModel {
705719
this.rowsExpandedChange.emit(index);
706720
}
707721

722+
/**
723+
* Expands / collapses all rows
724+
*
725+
* @param value expanded state of the rows. `true` is expanded and `false` is collapsed
726+
*/
727+
expandAllRows(value = true) {
728+
if (this.data.length > 0) {
729+
for (let i = 0; i < this.data.length; i++) {
730+
if (this.isRowExpandable(i)) {
731+
this.rowsExpanded[i] = value;
732+
}
733+
}
734+
735+
if (value) {
736+
this.rowsExpandedAllChange.emit();
737+
} else {
738+
this.rowsCollapsedAllChange.emit();
739+
}
740+
}
741+
}
742+
708743
/**
709744
* Gets the true index of a row based on it's relative position.
710745
* Like in Python, positive numbers start from the top and

src/table/table.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ import { TableRowSize } from "./table.types";
187187
[sortable]="sortable"
188188
(deselectAll)="onDeselectAll()"
189189
(selectAll)="onSelectAll()"
190+
(expandAllRows)="model.expandAllRows(true)"
191+
(collapseAllRows)="model.expandAllRows(false)"
190192
(sort)="doSort($event)"
191193
[checkboxHeaderLabel]="getCheckboxHeaderLabel()"
192194
[filterTitle]="getFilterTitle()"

0 commit comments

Comments
 (0)