Table Design
Edit on GitHubThis document describes the Table Design in the Components Library.
Overview
A Table Component is an arrangement of data in rows and columns, or possibly in a more complex structure (with sorting, filtering, pagination, row selections, infinite scrolling). It is an essential building block of a user interface.
A basic Table Component is <spy-table [config]="config"></spy-table>
where config
is:
dataSource
—the Datasource configuration from which the data is taken.columns
—an array of columns configuration.
const config: TableConfig = {
dataSource: {
// transforms input data via Data Transformer service
type: DatasourceType,
transform?: DataTransformerConfig,
},
columns: [
{ id: 'col1', title: 'Column #1' },
{ id: 'col2', title: 'Column #2' },
{ id: 'col3', title: 'Column #3' },
],
};
Architecture
Check out the table architecture diagram for better understanding:
Configuration
A Table Component is configured via Table Configuration that sets up how the table should behave and look like.
Datasources
To render data, the Table must receive it via Datasources that are registered by the user and then configured using the Table Configuration.
Features
Every other piece of functionality is extracted into the Table Feature:
- A Table Feature is an Angular Component that encapsulates a specific extension of the Core Table.
- Core Table contains specific placeholders in its view that Table Feature may target to render its piece of UI.
- Most of the common table functionality already exists as the Table Feature and may be used in the project.
To use a Feature component, register an Angular Module that implements the ModuleWithFeature
interface in the Root Module
using TableModule.withFeatures()
under the key that serves as its configuration key:
@NgModule({
imports: [
TableModule.withFeatures({
pagination: () => import('@spryker/table.feature.pagination').then(
(m) => m.TablePaginationFeatureModule,
),
}),
],
})
export class AppModule {}
Columns
Columns in a Table are defined by the Column Type and rendered within the columns (text, image, link). A new Column Type may be created and registered to the table.
A Column component must implement TableColumn
interface with the defined config and then be registered to the Root Module via TableModule.withColumnComponents()
:
@NgModule({
imports: [
TableModule.withColumnComponents({
text: TableColumnTextComponent,
}),
// Table Column Type Modules
TableColumnTextModule,
],
})
export class AppModule {}
Filters
A Table Component does not contain any filters a table usually has (filtering, searching). The Core Table Component has just a view of the columns and data and has built-in sorting.
To use Filter components, the Table Module must implement a specific interface (TableConfig) and then be registered to the Root Module via TableModule.withFilterComponents()
:
@NgModule({
imports: [
TableFiltersFeatureModule.withFilterComponents({
select: TableFilterSelectComponent,
}),
// Table Filter Modules
TableFilterSelectModule,
],
})
export class AppModule {}
Actions
There is a way to trigger some Actions while user interacts with the Table.
A few common Table Features that can trigger actions are available in the UI library:
- Row actions—renders a dropdown menu that contains actions applicable to the table row and on click triggers an Action which must be registered.
- Batch actions—allows triggering batch/multiple actions from rows.
Interfaces
The following interfaces are intended for the Table configuration:
export interface TableColumn extends Partial<TableColumnTypeDef> {
id: string;
title: string;
displayKey?: string;
width?: string;
multiRenderMode?: boolean;
multiRenderModeLimit?: number;
emptyValue?: string;
sortable?: boolean;
searchable?: boolean;
}
export interface TableColumnTypeDef {
type?: TableColumnType;
typeOptions?: TableColumnTypeOptions;
typeChildren?: TableColumnTypeDef[];
typeOptionsMappings?: TableColumnTypeOptionsMappings;
}
export interface TableColumnTypeOptions {
[key: string]: any;
}
interface TableColumnTypeOptionsMappings {
// Map of option values to new values
[optionName: string]: Record<string, any>;
}
export interface TableColumnTypeRegistry {
// Key is type string—value is type config class
'layout-flat': LayoutFlatConfig;
}
export type TableColumnType = keyof TableColumnTypeRegistry;
export interface TableHeaderContext {
config: TableColumn;
i: number;
}
export interface TableColumnContext extends AnyContext {
value: TableDataValue;
displayValue?: unknown;
row: TableDataRow;
config: TableColumn;
i: number;
j: number;
}
export interface TableColumnTplContext extends TableColumnContext {
$implicit: TableColumnContext['value'];
}
export interface TableColumnComponent<C = any> {
config?: C;
context?: TableColumnContext;
}
export type TableColumnComponentDeclaration = {
[P in keyof TableColumnTypeRegistry]?: Type<TableColumnComponent<TableColumnTypeRegistry[P] extends object
? TableColumnTypeRegistry[P]
: any>>;
};
export type TableColumns = TableColumn[];
export type TableDataValue = unknown | unknown[];
export type TableDataRow = Record<TableColumn['id'], TableDataValue>;
export interface TableData<T extends TableDataRow = TableDataRow> {
data: T[];
total: number;
page: number;
pageSize: number;
}
export interface TableConfig {
dataSource: DatasourceConfig;
columnsUrl?: string;
columns?: TableColumns;
// Features may expect it's config under it's namespace
[featureName: string]: TableFeatureConfig | unknown;
}
export type ColumnsTransformer = (
cols: TableColumns,
) => Observable<TableColumns>;
export type TableDataConfig = Record<string, unknown>;
export interface SortingCriteria {
sortBy?: string;
sortDirection?: 'asc' | 'desc';
}
export type TableEvents = Record<string, ((data: unknown) => void) | undefined>;
export interface TableComponent {
tableId?: string;
config?: TableConfig;
events: TableEvents;
config$: Observable<TableConfig>;
columns$: Observable<TableColumns>;
data$: Observable<TableData>;
isLoading$: Observable<boolean>;
tableId$: Observable<string>;
features$: Observable<TableFeatureComponent<TableFeatureConfig>[]>;
tableElementRef: ElementRef<HTMLElement>;
injector: Injector;
updateRowClasses(rowIdx: string, classes: Record<string, boolean>): void;
setRowClasses(rowIdx: string, classes: Record<string, boolean>): void;
on(feature: string, eventName?: string): Observable<unknown>;
findFeatureByName(name: string): Observable<TableFeatureComponent>;
findFeatureByType<T extends TableFeatureComponent>(
type: Type<T>,
): Observable<T>;
}
export enum TableFeatureLocation {
top = 'top',
beforeTable = 'before-table',
header = 'header',
headerExt = 'header-ext',
beforeRows = 'before-rows',
beforeColsHeader = 'before-cols-header',
beforeCols = 'before-cols',
cell = 'cell',
afterCols = 'after-cols',
afterColsHeader = 'after-cols-header',
afterRows = 'after-rows',
afterTable = 'after-table',
bottom = 'bottom',
hidden = 'hidden',
}
export interface TableRowActionRegistry {
// Key is action string—value is action options type
}
export type TableRowAction = keyof TableRowActionRegistry;
export interface TableRowActionHandler {
handleAction(actionEvent: TableActionTriggeredEvent): void;
}
export interface TableRowActionsDeclaration {
[type: string]: TableRowActionHandler;
}
export interface TableRowClickEvent {
row: TableDataRow;
event: Event;
}
Thank you!
For submitting the form