Skip to main content

io.display.table

Displays tabular data.

Usage

await io.display.table("Albums", {
helpText: "Includes the artist and its year of release.",
data: [
{
album: "Exile on Main Street",
artist: "The Rolling Stones",
year: 1972,
},
{
album: "Thriller",
artist: "Michael Jackson",
year: 1982,
},
{
album: "Enter the Wu-Tang (36 Chambers)",
artist: "Wu-Tang Clan",
year: 1993,
},
],
});
interval.com

Props

columns

(string | object)[] Required

Optional array of column definitions. If not provided, the object keys will be used.

accessorKey

string Optional

Column accessor key. At least one of `accessorKey` or `renderCell` must be specified.

label

string Required

Column header label.

renderCell

(row: T) => string | object Optional

Function that receives the row as argument and returns either a string value, or an object for advanced customization. See the function definition below for available properties. At least one of `renderCell` or `accessorKey` must be specified.
renderCell: (row: T) => string | {
// the visible cell label
label?: string | number | boolean | Date;
// an optional underlying value for sorting
value?: string | number | boolean | Date;
// links the cell's contents to an external URL
url?: string;
// links the cell's contents to another route
action?: string;
// arbitrary key/value pairs to send to the linked route
params?: Record<string, any>;
// a visible image to be displayed in the cell
// must contain either `url` or `buffer`
image?: {
// a URL to the image
url?: string
// a buffer containing the image contents
buffer?: Buffer
// the image alt tag
alt?: string
// the size of the image
size?: "thumbnail" | "small" | "medium" | "large"
}
}

data

T[] Optional

Array of table data. Values are objects with arbitrary keys/values. Must be specified if `getData` is not specified.

defaultPageSize

number Optional

The default page size for the paginated table. Pass `Infinity` to disable pagination by default.

getData

(state) => Promise<{ data: T[], totalRecords?: number }> Optional

An async function that receives the current table state as its only argument, and returns an array of `data` and an optional `totalRecords` count.
getData: (
state: {
// the user's search query, may be an empty string
queryTerm?: string,
// the `accessorKey` for the column being sorted
sortColumn?: string,
// the sort direction for the column being sorted
sortDirection?: "asc" | "desc",
// the number of records to skip for the current
// `queryTerm` and sorting, for pagination
offset: number,
// the current page size, should return at least this
// many records if available
pageSize: number
}
}) => Promise<{
// the underlying data records for this table state
data: T[],
// the total number of records available for this queryTerm, if known
totalRecords?: number
}>

helpText

string Optional

Secondary label providing an additional description of the data.

isAsync

boolean Optional

orientation

'horizontal' | 'vertical' Optional

Whether to render the table with records displayed as rows ("horizontal") or columns ("vertical"). Defaults to "horizontal".

rowMenuItems

(row: T) => object[] Optional

Optional array of items to display in a dropdown menu next to each row. Available beginning with SDK v0.25.0.
rowMenuItems: (row: T) => {
// the visible menu item label
label: string;
// links the menu item to an external URL
url?: string;
// arbitrary key/value pairs to send to the linked action
// links the menu item to another route
action?: string;
// arbitrary key/value pairs to send to the linked route
params?: Record<string, any>;
// disables the menu item
disabled?: boolean
// the style of the item
theme?: 'danger' | undefined
}[]

totalRecords

number Optional

Returns

null

Examples

Customizing output

The default behavior shown above is to display all of the fields in each record. To customize the display of records, a columns property can be provided. The columns property can contain an array of data property names, and only those columns will be displayed in the table.

await io.display.table("Users", {
data: [
{
email: "carsta.rocha@example.com",
phone_number: "(60) 1416-4953",
birthdate: "1993-08-04",
first_name: "Carsta",
last_name: "Rocha",
image: "https://example.com/photos/21351234.jpg",
website_url: "https://example.com",
},
{
email: "irene.morales@example.org",
phone_number: "625-790-958",
birthdate: "1982-04-28",
first_name: "Irene",
last_name: "Morales",
image: "https://example.com/photos/8321527.jpg",
website_url: "https://example.org",
},
],
columns: ["first_name", "last_name", "email"],
});

The columns array can also contain definition objects, with a label property and either an accessorKey string or a renderCell callback that returns an object with optional label, value, url, action string properties, and optional params and image object properties.

await io.display.table("Users", {
data: [
{
email: "carsta.rocha@example.com",
phone_number: "(60) 1416-4953",
birthdate: "1993-08-04",
first_name: "Carsta",
last_name: "Rocha",
image: "https://example.com/photos/21351234.jpg",
website_url: "https://example.com",
},
{
email: "irene.morales@example.org",
phone_number: "625-790-958",
birthdate: "1982-04-28",
first_name: "Irene",
last_name: "Morales",
image: "https://example.com/photos/8321527.jpg",
website_url: "https://example.org",
},
],
columns: [
{
label: "Name",
renderCell: row => `${row.first_name} ${row.last_name}`,
},
{
label: "Phone number",
accessorKey: "phone_number",
},
{
label: "Photo",
renderCell: row => ({
image: {
url: row.image,
alt: `${row.first_name} ${row.last_name} profile photo`,
size: "small",
},
}),
},
{
label: "Birth date",
renderCell: row => {
const [y, m, d] = row.birthdate.split("-").map(s => Number(s));
const birthDate = new Date(y, m - 1, d);
return {
label: birthDate.toLocaleDateString(),
value: birthDate,
};
},
},
{
label: "Website",
renderCell: row => ({
label: row.website_url,
url: row.website_url,
}),
},
{
label: "Edit action",
renderCell: row => ({
label: "Edit user",
action: "edit_user",
params: {
email: row.email,
},
}),
},
],
orientation: "horizontal",
});

If the renderCell callback returns an action property, a link will be generated to the action with that slug. A params object can also be defined, this object will be passed to the action's ctx.params context value.

tip

The two forms shown above can be combined within a single columns definition.

Adding menus

Beginning with SDK v0.25.0, each row can be given a dropdown menu using the rowMenuItems property, a function that provides the current row as the only argument and returns an array of menu items. Menu items are typically links to other actions.

await io.display.table("Albums", {
data: albums,
columns: ["album", "artist", "year"],
rowMenuItems: row => [
{
label: "Edit metadata",
action: "edit_album",
params: { id: row.id },
},
{
label: "Listen on Spotify",
// external URLs automatically open in a new tab.
url: `https://open.spotify.com/album/${row.spotifyId}`,
},
],
});
interval.com

Asynchronous data

tip

Best for situations like:

  • Large data sets of thousands of records
  • Fetching additional data from external APIs like Stripe

Beginning with SDK v0.31.0, data can be fetched asynchronously depending on user input like searching and page navigation. This allows advanced use cases like displaying large or external data sets without needing to fetch the entire set of records up front.

Fetching data asynchronously prevents many user experience enhancements like caching table data, client-side searching and pagination, and requires more code to account for search, sorting, and pagination. When in doubt, start with the synchronous data property and use this when the situation requires it.

info

Columns in async data tables are only available for sorting if the column is statically defined with an accessorKey property or via string shorthand.

await io.display.table("Purchases", {
getData: async ({
queryTerm,
sortColumn,
sortDirection,
offset,
pageSize,
}) => {
const where = {
OR: [
{
buyer: {
name: {
search: queryTerm,
},
},
},
{
product: {
name: {
search: queryTerm,
},
},
},
],
};

let orderBy;

if (sortColumn && sortDirection) {
orderBy = {
[sortColumn]: sortDirection,
};
}

const data = await db.purchases.find({
where,
skip: offset,
limit: pageSize,
orderBy,
});

const totalRecords = await db.purchases.count({
where,
});

return {
data,
totalRecords,
};
},
columns: [
{
label: "Buyer",
renderCell: row => row.buyer.name,
},
{
label: "Product",
renderCell: row => row.product.name,
},
{
label: "Amount",
accessorKey: "totalPrice",
},
],
});
danger

Be sure to safely sanitize, escape, or prepare all values before passing them to any database queries.

Did this section clearly explain what you wanted to learn?