Skip to content

Commit

Permalink
feat(stark-ui): add custom styling to stark table component
Browse files Browse the repository at this point in the history
  - added properties to stark-table component to allow for custom styling
  - added tests for new functionality
  - added demo component demonstrating new functionality
  - removed className property from `StarkTableColumnProperties`
  - moved cellClassNameFn property to cellClassName in `StarkTableColumnProperties`

ISSUES CLOSED: #523
  • Loading branch information
carlo-nomes committed Nov 19, 2018
1 parent 588cfc6 commit dd91254
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ import { StarkTableColumnPriority } from "./column-priority.intf";
* Definition of a column in the Stark Table
*/
export interface StarkTableColumnProperties {
/**
* Class(es) to be set to every cell of the column.
*/
cellClassName?: string;

/**
* Function that returns class(es) to be set to an specific cell. It can be used to set different classes
* depending on the row, value and or columnName. This function is called with 3 parameters:
* @param value - The value of the cell
* @param row - The row object that contains the cell
* @param columnName - The column that the cell belongs to
*
* This could also be a static string with class(es)
*/
cellClassNameFn?: (value: any, row?: any, columnName?: string) => string;
cellClassName?: ((value: any, row?: any, columnName?: string) => string) | string;

/**
* Function that returns a formatted value (string) to be set in the cell. It can be used to set different formats
Expand All @@ -27,13 +24,6 @@ export interface StarkTableColumnProperties {
*/
cellFormatter?: (value: any, row?: any, columnName?: string) => string;

/**
* Class(es) to be set to the colgroup>col element of the column.
* The CSS linked to the style can only be applied to 'border', 'background', 'width' and 'visibility' properties.
* See http://www.w3.org/TR/CSS2/tables.html#columns
*/
className?: string;

/**
* Function that returns
* 1 : if obj1 > obj2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<ng-container matColumnDef>
<!-- custom header that supports multi-column sorting -->
<!-- TODO: implement a MultiSort directive based on the Angular Material's MatSort once this is solved: https://github.com/angular/material2/issues/7226 -->
<th mat-header-cell *matHeaderCellDef [ngClass]="{'sortable': sortable, 'filtering': filterValue}">
<th mat-header-cell *matHeaderCellDef [ngClass]="getHeaderClassNames()">
<div class="header-cell-content">
<div [ngSwitch]="sortDirection" class="sort-header" (click)="onSortChange()">
<span>{{ getHeaderLabel() }}</span>
Expand Down Expand Up @@ -31,7 +31,7 @@
</th>
<!-- the column template defined by the user will be displayed here -->
<!-- and it will receive the right context containing the displayedValue and the row data-->
<td mat-cell *matCellDef="let rowItem">
<td mat-cell *matCellDef="let rowItem" [ngClass]="getCellClassNames(rowItem)">
<ng-container
*ngTemplateOutlet="columnTemplate; context:{ $implicit: { rowData: rowItem, displayedValue: getDisplayedValue(rowItem) } }"></ng-container>
</td>
Expand Down
51 changes: 51 additions & 0 deletions packages/stark-ui/src/modules/table/components/column.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class StarkTableColumnComponent extends AbstractStarkUiComponent {
public get name(): string {
return this._name;
}

public set name(name: string) {
this._name = name;
this.columnDef.name = name;
Expand Down Expand Up @@ -120,6 +121,19 @@ export class StarkTableColumnComponent extends AbstractStarkUiComponent {
@Input()
public sortPriority: number;

/**
* A function to generate classNames for cells based on the value, its row and the name of the column.
* Or a static string with the classNames.
*/
@Input()
public cellClassName?: ((value: any, row?: any, columnName?: string) => string) | string;

/**
* A static className for the header
*/
@Input()
public headerClassName?: string;

/**
* Output that will emit a specific column whenever its filter value has changed
*/
Expand Down Expand Up @@ -224,4 +238,41 @@ export class StarkTableColumnComponent extends AbstractStarkUiComponent {
public onSortChange(): void {
this.sortChanged.emit(this);
}

/**
* Gets a the classes for a specific cell, based on the cellClassName Input and the cellClassNameFn function if it was given.
* @param row - The data object of the row the cell is in.
* @returns The classes for the cell.
*/
public getCellClassNames(row: any): string {
if (!this.cellClassName) {
return "";
}
if (typeof this.cellClassName === "string") {
return this.cellClassName;
}

const value: any = this.getRawValue(row);
return this.cellClassName(value, row, this.name);
}

/**
* Get the classes for a header
* @returns The classes for the header
*/
public getHeaderClassNames(): string {
const classes: string[] = [];

if (this.sortable) {
classes.push("sortable");
}
if (this.filterValue) {
classes.push("filtering");
}
if (this.headerClassName) {
classes.push(this.headerClassName);
}

return classes.join(" ");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
[filterable]="col.isFilterable"
[filterValue]="getColumnFilterValue(col.name)"
[compareFn]="col.compareFn"
[cellFormatter]="col.cellFormatter">
[cellFormatter]="col.cellFormatter"
[cellClassName]="col.cellClassName"
[headerClassName]="col.headerClassName">
<ng-template let-context>
<span>{{ context.displayedValue }}</span>
</ng-template>
Expand All @@ -76,6 +78,9 @@
</stark-table-column>

<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: isFixedHeaderEnabled"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-row
*matRowDef="let row; columns: displayedColumns; let i = index;"
[ngClass]="getRowClasses(row, i)">
</tr>
</table>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ import createSpy = jasmine.createSpy;

@Component({
selector: `host-component`,
template: `<stark-table [columnProperties]="columnProperties"
[data]="dummyData"
[filter]="tableFilter"
[fixedHeader]="fixedHeader"
[multiSort]="multiSort"
[multiSelect]="multiSelect"
[orderProperties]="orderProperties"
[tableRowsActionBarConfig]="tableRowsActionBarConfig">
</stark-table>`
template: `
<stark-table [columnProperties]="columnProperties"
[data]="dummyData"
[filter]="tableFilter"
[fixedHeader]="fixedHeader"
[multiSort]="multiSort"
[multiSelect]="multiSelect"
[orderProperties]="orderProperties"
[tableRowsActionBarConfig]="tableRowsActionBarConfig"
[rowClassNameFn]="rowClassNameFn">
</stark-table>`
})
class TestHostComponent {
@ViewChild(StarkTableComponent)
Expand All @@ -50,7 +52,9 @@ class TestHostComponent {
public tableRowsActionBarConfig: StarkActionBarConfig;
public tableFilter: StarkTableFilter;
public orderProperties?: string[];
public rowClassNameFn?: (row: any, index: number) => string;
}

/* tslint:disable:no-big-function */
describe("TableComponent", () => {
let component: StarkTableComponent;
Expand Down Expand Up @@ -893,4 +897,58 @@ describe("TableComponent", () => {
expect((<StarkTableColumnFilter[]>component.filter.columns)[0].filterValue).toBe(dummyColumnFilterValue);
});
});

describe("setStyling", () => {
const dummyData: any[] = [{ id: 1, description: "dummy 1" }, { id: 2, description: "dummy 2" }, { id: 3, description: "dummy 3" }];
const returnEvenAndOdd: (row: any, index: number) => string = (_row: any, index: number): string =>
(index + 1) % 2 === 0 ? "even" : "odd"; // offset index with 1

beforeEach(() => {
hostComponent.rowClassNameFn = returnEvenAndOdd;
hostComponent.columnProperties = [
{ name: "id", cellClassName: (value: any) => (value === 1 ? "one" : "") },
{ name: "description", cellClassName: "description-body-cell", headerClassName: "description-header-cell" }
];
hostComponent.dummyData = dummyData;

hostFixture.detectChanges(); // trigger data binding
component.ngAfterViewInit();
});

describe("setRowClass", () => {
it("first row should have class 'odd", () => {
const tableElement: HTMLElement = hostFixture.nativeElement;
const firstRow: HTMLElement | null = tableElement.querySelectorAll<HTMLElement>("tbody tr").item(0);
expect(firstRow && firstRow.classList).toContain("odd");
expect(firstRow && firstRow.classList).not.toContain("even");
});

it("second row should have class 'even'", () => {
const tableElement: HTMLElement = hostFixture.nativeElement;
const secondRow: HTMLElement | null = tableElement.querySelectorAll<HTMLElement>("tbody tr").item(1);
expect(secondRow && secondRow.classList).toContain("even");
expect(secondRow && secondRow.classList).not.toContain("odd");
});
});

it("cell id should have class 'one'", () => {
const tableElement: HTMLElement = hostFixture.nativeElement;
const descriptionCell: HTMLElement | null = tableElement.querySelector("tbody tr:nth-child(1) td:nth-child(1)"); // select the id cells
expect(descriptionCell && descriptionCell.classList).toContain("one");
});

it("description header cell should have class 'description-header-cell'", () => {
const tableElement: HTMLElement = hostFixture.nativeElement;
const descriptionCell: HTMLElement | null = tableElement.querySelector("thead th:nth-child(2)"); // select the id cells
expect(descriptionCell && descriptionCell.classList).toContain("description-header-cell");
});

it("description body cells should have class 'description-body-cell'", () => {
const tableElement: HTMLElement = hostFixture.nativeElement;
const descriptionCells: NodeListOf<HTMLElement> = tableElement.querySelectorAll("tbody td:nth-child(2)"); // select the id cells
descriptionCells.forEach((descriptionCell: HTMLElement) =>
expect(descriptionCell && descriptionCell.classList).toContain("description-body-cell")
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ export class StarkTableComponent extends AbstractStarkUiComponent implements OnI
@Input()
public tableRowsActionBarConfig: StarkActionBarConfig;

/**
* Function to generate classNames for rows
*/
@Input()
public rowClassNameFn?: (row: any, index: number) => string;

/**
* Output event emitter that will emit the latest global filter value whenever it changes.
*/
Expand Down Expand Up @@ -333,7 +339,7 @@ export class StarkTableComponent extends AbstractStarkUiComponent implements OnI
if (changes["multiSelect"]) {
this.isMultiSelectEnabled = StarkComponentUtil.isInputEnabled(this.multiSelect);

if (this.isMultiSelectEnabled) {
if (this.isMultiSelectEnabled && this.displayedColumns) {
this.displayedColumns.unshift("select");
}
}
Expand Down Expand Up @@ -573,8 +579,12 @@ export class StarkTableComponent extends AbstractStarkUiComponent implements OnI
* In case there is a compareFn defined for any of the columns then such method is called to perform the custom sorting.
* FIXME: refactor this method to reduce its cognitive complexity
*/

/* tslint:disable-next-line:cognitive-complexity */
public sortData(): void {
if (!this.columns) {
return;
}
const sortableColumns: StarkTableColumnComponent[] = this.columns
.filter((columnToFilter: StarkTableColumnComponent) => columnToFilter.sortDirection)
.sort((column1: StarkTableColumnComponent, column2: StarkTableColumnComponent) => column1.sortPriority - column2.sortPriority);
Expand Down Expand Up @@ -714,6 +724,16 @@ export class StarkTableComponent extends AbstractStarkUiComponent implements OnI
return filterValueReset;
}

/**
* Gets the class for a specific row if a rowClassNameFn function has been given as an Input
* @param row - The data object passed to the row.
* @param index - The index of the row.
* @returns The className generated by the rowClassNameFn function
*/
public getRowClasses(row: any, index: number): string {
return typeof this.rowClassNameFn === "function" ? this.rowClassNameFn(row, index) : "";
}

/**
* @ignore
*/
Expand Down
14 changes: 14 additions & 0 deletions showcase/src/app/demo/table/demo-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ <h1 translate>SHOWCASE.DEMO.TABLE.WITH_TRANSCLUDED_ACTION_BAR</h1>
<header><h1 translate>SHOWCASE.DEMO.TABLE.WITH_FIXED_HEADER</h1></header>
</stark-table>
</example-viewer>

<example-viewer [extensions]="['HTML','TS','SCSS']"
filesPath="table/with-custom-styling"
exampleTitle="SHOWCASE.DEMO.TABLE.WITH_CUSTOM_STYLING">
<stark-table htmlId="table-custom-row"
[data]="dummyData"
[filter]="filter"
[paginationConfig]="paginationConfig1"
[columnProperties]="columnsPropertiesCustomStyling"
[rowClassNameFn]="getRowClassName"
>
<header><h1 translate>SHOWCASE.DEMO.TABLE.WITH_CUSTOM_STYLING</h1></header>
</stark-table>
</example-viewer>
</section>
<stark-reference-block [links]="referenceList"></stark-reference-block>

20 changes: 20 additions & 0 deletions showcase/src/app/demo/table/demo-table.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,23 @@
margin-bottom: 0;
}
}

.even td {
background-color: #d1e1ff;
}

.danger {
color: #7c002c;
}

.warning {
color: #ff9800;
}

.success {
color: #3c9f40;
}

.bold {
font-weight: bold;
}
29 changes: 21 additions & 8 deletions showcase/src/app/demo/table/demo-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ export class DemoTableComponent implements OnInit {
return 0;
};

public getRowClassName = (_row: any, index: number): string => (index % 2 === 0 ? "even" : "odd");

public getTitleCellClassName = (title: { value: number }): string =>
title.value < 5 ? "danger" : title.value < 9 ? "warning" : "success";

public dummyData: any[];
public columnsProperties: StarkTableColumnProperties[];
public columnsPropertiesCustomStyling: StarkTableColumnProperties[];
public customTableActions: StarkAction[];
public tableRowsActionBarConfig: StarkActionBarConfig;
public paginationConfig1: StarkPaginationConfig;
Expand Down Expand Up @@ -89,23 +95,30 @@ export class DemoTableComponent implements OnInit {
isFilterable: true
}
];

this.columns = [
this.columnsPropertiesCustomStyling = [
{
name: "id",
sortable: true
label: "id",
headerClassName: "bold",
isSortable: true,
isFilterable: true
},
{
name: "title",
sortable: true,
dataAccessor: (data: any): string => {
return "~" + data.title.label;
label: "Title",
cellFormatter: (value: any): string => {
return "~" + value.label;
},
compareFn: this.compareTitle
isSortable: true,
isFilterable: true,
compareFn: this.compareTitle,
cellClassName: this.getTitleCellClassName
},
{
name: "description",
sortable: true
label: "Description",
isSortable: true,
isFilterable: true
}
];

Expand Down
Loading

0 comments on commit dd91254

Please sign in to comment.