diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 2055507a32..5a1ba5bbd6 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -89,6 +89,7 @@ export default {
"CHECKBOX_HEADER": "Select all rows",
"CHECKBOX_ROW": "Select {{value}}",
"EXPAND_BUTTON": "Expand row",
+ "EXPAND_ALL_BUTTON": "Expand all rows",
"SORT_DESCENDING": "Sort rows by this header in descending order",
"SORT_ASCENDING": "Sort rows by this header in ascending order",
"ROW": "row"
diff --git a/src/table/head/table-head-expand.component.ts b/src/table/head/table-head-expand.component.ts
index f322843bb4..2c0e94448b 100644
--- a/src/table/head/table-head-expand.component.ts
+++ b/src/table/head/table-head-expand.component.ts
@@ -1,15 +1,47 @@
import {
Component,
- HostBinding
+ EventEmitter,
+ HostBinding,
+ Input,
+ Output
} from "@angular/core";
+import { I18n } from "carbon-components-angular/i18n";
+import { Observable } from "rxjs";
@Component({
// tslint:disable-next-line: component-selector
selector: "[cdsTableHeadExpand], [ibmTableHeadExpand]",
template: `
-
+
+
+
+
`
})
export class TableHeadExpand {
@HostBinding("class.cds--table-expand") hostClass = true;
+
+ @Input() showExpandAllToggle = false;
+
+ @Input() expanded = false;
+
+ @Output() expandedChange = new EventEmitter();
+
+ @HostBinding("attr.data-previous-value") get previousValue() {
+ return this.expanded ? "collapsed" : null;
+ }
+
+ protected _ariaLabel = this.i18n.getOverridable("TABLE.EXPAND_ALL_BUTTON");
+
+ constructor(protected i18n: I18n) { }
+
+ getAriaLabel(): Observable {
+ return this._ariaLabel.subject;
+ }
}
diff --git a/src/table/head/table-head.component.ts b/src/table/head/table-head.component.ts
index b758b4e045..569ae0872e 100644
--- a/src/table/head/table-head.component.ts
+++ b/src/table/head/table-head.component.ts
@@ -31,8 +31,11 @@ import { TableRowSize } from "../table.types";
cdsTableHeadExpand
*ngIf="model.hasExpandableRows()"
scope="col"
+ [showExpandAllToggle]="showExpandAllToggle"
[ngClass]="{'cds--table-expand-v2': stickyHeader}"
- [id]="model.getId('expand')">
+ [id]="model.getId('expand')"
+ [expanded]="model.expandableRowsCount() === model.expandedRowsCount()"
+ (expandedChange)="onExpandAllRowsChange($event)">
();
+ /**
+ * Emits if all rows are expanded.
+ *
+ * @param model
+ */
+ @Output() expandAllRows = new EventEmitter();
+ /**
+ * Emits if all rows are collapsed.
+ *
+ * @param model
+ */
+ @Output() collapseAllRows = new EventEmitter();
public scrollbarWidth = 0;
@@ -184,6 +201,14 @@ export class TableHead implements AfterViewInit {
}
}
+ onExpandAllRowsChange(expand: boolean) {
+ if (expand) {
+ this.expandAllRows.emit(this.model);
+ } else {
+ this.collapseAllRows.emit(this.model);
+ }
+ }
+
getCheckboxHeaderLabel(): Observable {
return this._checkboxHeaderLabel.subject;
}
diff --git a/src/table/stories/app-expansion-table.component.ts b/src/table/stories/app-expansion-table.component.ts
index eacc33d92b..a7a0e289b3 100644
--- a/src/table/stories/app-expansion-table.component.ts
+++ b/src/table/stories/app-expansion-table.component.ts
@@ -45,8 +45,15 @@ class CustomHeaderItem extends TableHeaderItem {
[striped]="striped"
(sort)="customSort($event)"
(rowClick)="onRowClick($event)"
- [isDataGrid]="isDataGrid">
+ [isDataGrid]="isDataGrid"
+ [showExpandAllToggle]="showExpandAllToggle">
+
+
+
+
+
+
`
})
export class ExpansionTableStory implements AfterViewInit {
@@ -58,6 +65,7 @@ export class ExpansionTableStory implements AfterViewInit {
@Input() sortable = true;
@Input() stickyHeader = false;
@Input() skeleton = false;
+ @Input() showExpandAllToggle = false;
@ViewChild("customHeaderTemplate")
protected customHeaderTemplate: TemplateRef;
diff --git a/src/table/table-model.class.spec.ts b/src/table/table-model.class.spec.ts
index 5aa297e289..7919f17dcc 100644
--- a/src/table/table-model.class.spec.ts
+++ b/src/table/table-model.class.spec.ts
@@ -760,4 +760,34 @@ describe("Table", () => {
expect(tableModel.header[1].data).toEqual("h2");
expect(tableModel.header[2].data).toEqual("h3");
});
+
+ it("should expand and collapse all rows", () => {
+ let tableModel = new TableModel();
+
+ spyOn(tableModel.rowsExpandedAllChange, "emit");
+ spyOn(tableModel.rowsCollapsedAllChange, "emit");
+
+ tableModel.header = [
+ new TableHeaderItem({data: "h1"}), new TableHeaderItem({data: "h2"})
+ ];
+ tableModel.data = [
+ [new TableItem({data: "A", expandedData: "EX1"}), new TableItem({data: "B"})],
+ [new TableItem({data: "C"}), new TableItem({data: "D"})],
+ [new TableItem({data: "E", expandedData: "EX2"}), new TableItem({data: "F"})],
+ [new TableItem({data: "G"}), new TableItem({data: "H"})]
+ ];
+
+ expect(tableModel.expandableRowsCount()).toBe(2);
+ expect(tableModel.expandedRowsCount()).toBe(0);
+
+ tableModel.expandAllRows(true);
+ expect(tableModel.rowsExpandedAllChange.emit).toHaveBeenCalledWith();
+ expect(tableModel.expandedRowsCount()).toBe(2);
+ expect(tableModel.rowsExpanded).toEqual([true, false, true, false]);
+
+ tableModel.expandAllRows(false);
+ expect(tableModel.rowsCollapsedAllChange.emit).toHaveBeenCalledWith();
+ expect(tableModel.expandedRowsCount()).toBe(0);
+ expect(tableModel.rowsExpanded).toEqual([false, false, false, false]);
+ });
});
diff --git a/src/table/table-model.class.ts b/src/table/table-model.class.ts
index 3af4828bad..73be6d1bc7 100644
--- a/src/table/table-model.class.ts
+++ b/src/table/table-model.class.ts
@@ -69,6 +69,8 @@ export class TableModel implements PaginationModel {
dataChange = new EventEmitter();
rowsSelectedChange = new EventEmitter();
rowsExpandedChange = new EventEmitter();
+ rowsExpandedAllChange = new EventEmitter();
+ rowsCollapsedAllChange = new EventEmitter();
/**
* Gets emitted when `selectAll` is called. Emits false if all rows are deselected and true if
* all rows are selected.
@@ -413,6 +415,18 @@ export class TableModel implements PaginationModel {
return this.data.some(data => data.some(d => d && d.expandedData)); // checking for some in 2D array
}
+ /**
+ * Number of rows that can be expanded.
+ *
+ * @returns number
+ */
+ expandableRowsCount() {
+ return this.data.reduce((counter, _, index) => {
+ counter = (this.isRowExpandable(index)) ? counter + 1 : counter;
+ return counter;
+ }, 0);
+ }
+
isRowExpandable(index: number) {
return this.data[index].some(d => d && d.expandedData);
}
@@ -705,6 +719,27 @@ export class TableModel implements PaginationModel {
this.rowsExpandedChange.emit(index);
}
+ /**
+ * Expands / collapses all rows
+ *
+ * @param value expanded state of the rows. `true` is expanded and `false` is collapsed
+ */
+ expandAllRows(value = true) {
+ if (this.data.length > 0) {
+ for (let i = 0; i < this.data.length; i++) {
+ if (this.isRowExpandable(i)) {
+ this.rowsExpanded[i] = value;
+ }
+ }
+
+ if (value) {
+ this.rowsExpandedAllChange.emit();
+ } else {
+ this.rowsCollapsedAllChange.emit();
+ }
+ }
+ }
+
/**
* Gets the true index of a row based on it's relative position.
* Like in Python, positive numbers start from the top and
diff --git a/src/table/table.component.ts b/src/table/table.component.ts
index 9e847c6fe6..b804e50ff7 100644
--- a/src/table/table.component.ts
+++ b/src/table/table.component.ts
@@ -187,6 +187,8 @@ import { TableRowSize } from "./table.types";
[sortable]="sortable"
(deselectAll)="onDeselectAll()"
(selectAll)="onSelectAll()"
+ (expandAllRows)="model.expandAllRows(true)"
+ (collapseAllRows)="model.expandAllRows(false)"
(sort)="doSort($event)"
[checkboxHeaderLabel]="getCheckboxHeaderLabel()"
[filterTitle]="getFilterTitle()"
@@ -195,6 +197,7 @@ import { TableRowSize } from "./table.types";
[selectAllCheckboxSomeSelected]="selectAllCheckboxSomeSelected"
[showSelectionColumn]="showSelectionColumn"
[enableSingleSelect]="enableSingleSelect"
+ [showExpandAllToggle]="showExpandAllToggle"
[skeleton]="skeleton"
[sortAscendingLabel]="sortAscendingLabel"
[sortDescendingLabel]="sortDescendingLabel"
@@ -384,6 +387,11 @@ export class Table implements OnInit, AfterViewInit, OnDestroy {
@Input() noBorder = true;
+ /**
+ * Set to `true` to show expansion toggle when table consists of row expansions
+ */
+ @Input() showExpandAllToggle = false;
+
get isDataGrid(): boolean {
return this._isDataGrid;
}
diff --git a/src/table/table.stories.ts b/src/table/table.stories.ts
index 1864f04e54..1260862ab6 100644
--- a/src/table/table.stories.ts
+++ b/src/table/table.stories.ts
@@ -482,7 +482,8 @@ const ExpansionTemplate = (args) => ({
[stickyHeader]="stickyHeader"
[skeleton]="skeleton"
[striped]="striped"
- [isDataGrid]="isDataGrid">
+ [isDataGrid]="isDataGrid"
+ [showExpandAllToggle]="showExpandAllToggle">
`
@@ -491,7 +492,8 @@ export const WithExpansion = ExpansionTemplate.bind({});
WithExpansion.args = {
...getProps({
description: "With expansion"
- }, "args")
+ }, "args"),
+ showExpandAllToggle: false
};
const DyanmicContentTemplate = (args) => ({
|