Skip to content

Commit

Permalink
Merge pull request #3014 from eduardmarcinco/feat/table-expand-all
Browse files Browse the repository at this point in the history
feat: allow users to expand all table rows
  • Loading branch information
zvonimirfras authored Oct 15, 2024
2 parents 75422dc + 984f3c9 commit c18d9b7
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
36 changes: 34 additions & 2 deletions src/table/head/table-head-expand.component.ts
Original file line number Diff line number Diff line change
@@ -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: `
<ng-content></ng-content>
<button
*ngIf="showExpandAllToggle"
class="cds--table-expand__button"
[attr.aria-label]="getAriaLabel() | async"
(click)="expandedChange.emit(!expanded)">
<svg cdsIcon="chevron--right" size="16" class="cds--table-expand__svg"></svg>
</button>
<ng-container *ngIf="!showExpandAllToggle">
<ng-content></ng-content>
</ng-container>
`
})
export class TableHeadExpand {
@HostBinding("class.cds--table-expand") hostClass = true;

@Input() showExpandAllToggle = false;

@Input() expanded = false;

@Output() expandedChange = new EventEmitter<boolean>();

@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<string> {
return this._ariaLabel.subject;
}
}
27 changes: 26 additions & 1 deletion src/table/head/table-head.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)">
</th>
<th
*ngIf="!skeleton && showSelectionColumn && enableSingleSelect"
Expand Down Expand Up @@ -102,6 +105,8 @@ export class TableHead implements AfterViewInit {

@Input() stickyHeader = false;

@Input() showExpandAllToggle = false;

/**
* Setting sortable to false will disable all headers including headers which are sortable. Is is
* possible to set the sortable state on the header item to disable/enable sorting for only some headers.
Expand Down Expand Up @@ -160,6 +165,18 @@ export class TableHead implements AfterViewInit {
* @param model
*/
@Output() deselectAll = new EventEmitter<TableModel>();
/**
* Emits if all rows are expanded.
*
* @param model
*/
@Output() expandAllRows = new EventEmitter<TableModel>();
/**
* Emits if all rows are collapsed.
*
* @param model
*/
@Output() collapseAllRows = new EventEmitter<TableModel>();

public scrollbarWidth = 0;

Expand All @@ -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<string> {
return this._checkboxHeaderLabel.subject;
}
Expand Down
10 changes: 9 additions & 1 deletion src/table/stories/app-expansion-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ class CustomHeaderItem extends TableHeaderItem {
[striped]="striped"
(sort)="customSort($event)"
(rowClick)="onRowClick($event)"
[isDataGrid]="isDataGrid">
[isDataGrid]="isDataGrid"
[showExpandAllToggle]="showExpandAllToggle">
</cds-table>
<br>
<button cdsButton="primary" size="sm" (click)="model.expandAllRows(true)">Expand all rows</button>
<button cdsButton="secondary" size="sm" (click)="model.expandAllRows(false)">Collapse all rows</button>
`
})
export class ExpansionTableStory implements AfterViewInit {
Expand All @@ -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<any>;
Expand Down
30 changes: 30 additions & 0 deletions src/table/table-model.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
});
});
35 changes: 35 additions & 0 deletions src/table/table-model.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export class TableModel implements PaginationModel {
dataChange = new EventEmitter();
rowsSelectedChange = new EventEmitter<number>();
rowsExpandedChange = new EventEmitter<number>();
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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/table/table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()"
Expand All @@ -195,6 +197,7 @@ import { TableRowSize } from "./table.types";
[selectAllCheckboxSomeSelected]="selectAllCheckboxSomeSelected"
[showSelectionColumn]="showSelectionColumn"
[enableSingleSelect]="enableSingleSelect"
[showExpandAllToggle]="showExpandAllToggle"
[skeleton]="skeleton"
[sortAscendingLabel]="sortAscendingLabel"
[sortDescendingLabel]="sortDescendingLabel"
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 4 additions & 2 deletions src/table/table.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,8 @@ const ExpansionTemplate = (args) => ({
[stickyHeader]="stickyHeader"
[skeleton]="skeleton"
[striped]="striped"
[isDataGrid]="isDataGrid">
[isDataGrid]="isDataGrid"
[showExpandAllToggle]="showExpandAllToggle">
</app-expansion-table>
</cds-table-container>
`
Expand All @@ -491,7 +492,8 @@ export const WithExpansion = ExpansionTemplate.bind({});
WithExpansion.args = {
...getProps({
description: "With expansion"
}, "args")
}, "args"),
showExpandAllToggle: false
};

const DyanmicContentTemplate = (args) => ({
Expand Down

0 comments on commit c18d9b7

Please sign in to comment.