Skip to main content

Custom Datatable

The datatable component has a lot of configuration attributes, and some can be difficult to get right. In this example we want a customization of the datatable component that shows data from the Start Wars API.

We don't want to setup the urls or the columns everytime we use this widget, we only want to choose if the table will show planets, people, films, starships, vehicles or species.

import * as React from "react";
import { Model, _ } from "@flowable/forms";
import { FormCache } from "@flowable/forms/packages/flowable-forms/src/flw/FormCache";

const definition = {
extraSettings: {
clientPagination: false,
url: "{{$item.url}}",
dataSource: "Rest",
queryUrl: "https://swapi.dev/api/RESOURCE/?page={{$page+1}}",
pageSize: 10,
path: "results",
response: {
total: "count",
},
},
};
const DEFAULT_RESOURCE = "people";

const columns = {
planets: "name climate diameter gravity orbital_period population rotation_period surface_water terrain",
people: "name birth_year eye_color gender hair_color height mass skin_color",
films: "title director episode_id opening_crawl producer release_date",
starships: "name MGLT cargo_capacity consumables cost_in_credits crew hyperdrive_rating length manufacturer max_atmosphering_speed model passengers starship_class",
vehicles: "name cargo_capacity consumables cost_in_credits crew length manufacturer max_atmosphering_speed model passengers vehicle_class",
species: "name average_height average_lifespan classification designation eye_colors hair_colors language skin_colors",
};

export class CustomTable extends Model.FormComponent {
render() {
const DataTable = this.props.Components.dataTable;
return <DataTable {...this.props} />;
}
static $resolve(
unresolved: Model.Column,
scope: Model.Payload,
addData: Model.Payload,
Components: Model.ComponentStore,
formCache: FormCache,
currentPath: string
) {
const resolve = Components.panel.$resolve;
let resource = unresolved?.extraSettings?.resource;
let resourceColumns = columns[resource];
if (!resourceColumns) {
resourceColumns = columns[DEFAULT_RESOURCE];
resource = DEFAULT_RESOURCE;
}
const colDefinition = {
extraSettings: {
queryUrl: definition.extraSettings.queryUrl.replace("RESOURCE", resource),
columns: resourceColumns.split(" ").map((x: string) => ({ label: x, accessor: x })),
},
};
return resolve(_.mergeDeepAll([unresolved, definition, colDefinition]), scope, addData, Components, formCache, currentPath);
}
}

Now we are using a class instead of a function and we are implementing the static $resolve method.

The idea is to enrich original definition of this component with some pre-defined attributes.

For example

{
"type": "mysuiteCustomTable",
"enabled": true,
"visible": true,
"extraSettings": {
"resource": "films"
},
"size": 12
}

Would become


{
"type": "mysuiteCustomTable",
"enabled": true,
"visible": true,
"extraSettings": {
"resource": "films",
"clientPagination": false,
"dataSource": "Rest",
"queryUrl": "https://swapi.dev/api/films/?page=1",
"pageSize": 10,
"path": "results",
"response": {
"total": "count"
},
"columns": [
{
"label": "title",
"accessor": "title"
},
{
"label": "director",
"accessor": "director"
},
{
"label": "episode_id",
"accessor": "episode_id"
},
{
"label": "opening_crawl",
"accessor": "opening_crawl"
},
{
"label": "producer",
"accessor": "producer"
},
{
"label": "release_date",
"accessor": "release_date"
}
]
},
"size": 12
}

This enriched definition is enough to render a datatable component. And we do it with:

render() {
const DataTable = this.props.Components.dataTable;
return <DataTable {...this.props} />;
}

But there's still a step missing, the datatable requires this definition to be $resolved. Flowable forms knows how to resolve most of the components, but it allows components to provide it's own static $resolve function. That's specially helpful to implement components that contain other components, or like in this case to enrich the definition before the resolution.

Instead of implementing ourselves the $resolve function we are going to use the existing function in props.Components.panel.$resolve

static $resolve( unresolved: Model.Column, scope: Model.Payload, addData: Model.Payload, Components: Model.ComponentStore, formCache: FormCache, currentPath: string) {
// $resolve in Panel knows how to resolve most components
const resolve = Components.panel.$resolve;
// Merge the original definition with the static attributes and the columns of the chosen resource
const colDefinition = {
extraSettings: {
queryUrl: definition.extraSettings.queryUrl.replace("RESOURCE", unresolved?.extraSettings?.resource),
columns: columns[resource].split(" ").map((x: string) => ({ label: x, accessor: x })),
},
};
const completeDefinition = _.mergeDeepAll([unresolved, definition, colDefinition]);
// call resolve with the resulting definition
return resolve(completeDefinition, scope, addData, Components, formCache, currentPath);
}

The engine will resolve the whole form definition tree and then it will instantiate this component class. The component will receive the resolved config, including the changes we did to it.