io.display.grid
Displays data in a grid layout.
info
Added in v0.36.0.
Grid items can include a label, description, image, and options menu, and can optionally link to another page, action, or external URL.
Grid item size can be controlled using the idealColumnWidth
property. Interval will calculate a column width that is as close as possible to that number while factoring in gutter size and window width.
Images default to a 16:9 aspect ratio with object-fit
set to cover
, and can be customized via the image.aspectRatio
and image.fit
properties respectively in the renderItem
callback.
Usage
- TypeScript
- JavaScript
await io.display.grid("Albums", {
idealColumnWidth: 180,
data: [
{
album: "Exile on Main Street",
artist: "The Rolling Stones",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/c/ca/ExileMainSt.jpg",
spotifyId: "1D0PTM0bg7skufClSUOxTP",
},
{
album: "Thriller",
artist: "Michael Jackson",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/5/55/Michael_Jackson_-_Thriller.png",
spotifyId: "2ANVost0y2y52ema1E9xAZ",
},
{
album: "Enter the Wu-Tang (36 Chambers)",
artist: "Wu-Tang Clan",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/5/53/Wu-TangClanEntertheWu-Tangalbumcover.jpg",
spotifyId: "6acGx168JViE5LLFR1rGRE",
},
],
renderItem: row => ({
label: row.album,
description: row.artist,
image: {
url: row.imageUrl,
aspectRatio: 1,
},
}),
});
await io.display.grid("Albums", {
idealColumnWidth: 180,
data: [
{
album: "Exile on Main Street",
artist: "The Rolling Stones",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/c/ca/ExileMainSt.jpg",
spotifyId: "1D0PTM0bg7skufClSUOxTP",
},
{
album: "Thriller",
artist: "Michael Jackson",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/5/55/Michael_Jackson_-_Thriller.png",
spotifyId: "2ANVost0y2y52ema1E9xAZ",
},
{
album: "Enter the Wu-Tang (36 Chambers)",
artist: "Wu-Tang Clan",
imageUrl:
"https://upload.wikimedia.org/wikipedia/en/5/53/Wu-TangClanEntertheWu-Tangalbumcover.jpg",
spotifyId: "6acGx168JViE5LLFR1rGRE",
},
],
renderItem: row => ({
label: row.album,
description: row.artist,
image: {
url: row.imageUrl,
aspectRatio: 1,
},
}),
});
Props
- TypeScript
- JavaScript
data
Optional
T[]
defaultPageSize
Optional
number
getData
Optional
(state) => Promise<{ data: T[], totalRecords?: number }>
getData: (
state: {
// the user's search query, may be an empty string
queryTerm?: string,
// 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 result set for this table state
data: T[],
// the total number of records available for this queryTerm, if known
totalRecords?: number
}>
helpText
Optional
string
idealColumnWidth
Optional
number
isFilterable
Optional
boolean
renderItem
Optional
(row: T) => object
renderItem: (row: T) => {
// the visible item label
title?: string | number | boolean | Date;
// an optional description
description?: string | number | boolean | Date;
// links the item to an external URL
url?: string;
// links the item to another action or page
route?: string;
// an image to be displayed in the cell
image?: {
// a URL to the image
url?: string
// the image alt tag
alt?: string
// the image aspect ratio, e.g. `1` or `16 / 9`, defaults to 16 / 9.
aspectRatio?: number
// whether the image should be resized to fill its container or
// be cropped to fit, defaults to 'cover'.
fit?: 'cover' | 'contain'
}
// an array of links to be displayed in a dropdown menu
menu?: {
// links to an external URL
url?: string;
// links to another page or action
route?: 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
}[]
}
Returns
null
data
Optional
T[]
defaultPageSize
Optional
number
getData
Optional
(state) => Promise<{ data: T[], totalRecords?: number }>
getData: (
state: {
// the user's search query, may be an empty string
queryTerm?: string,
// 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 result set for this table state
data: T[],
// the total number of records available for this queryTerm, if known
totalRecords?: number
}>
helpText
Optional
string
idealColumnWidth
Optional
number
isFilterable
Optional
boolean
renderItem
Optional
(row: T) => object
renderItem: (row: T) => {
// the visible item label
title?: string | number | boolean | Date;
// an optional description
description?: string | number | boolean | Date;
// links the item to an external URL
url?: string;
// links the item to another action or page
route?: string;
// an image to be displayed in the cell
image?: {
// a URL to the image
url?: string
// the image alt tag
alt?: string
// the image aspect ratio, e.g. `1` or `16 / 9`, defaults to 16 / 9.
aspectRatio?: number
// whether the image should be resized to fill its container or
// be cropped to fit, defaults to 'cover'.
fit?: 'cover' | 'contain'
}
// an array of links to be displayed in a dropdown menu
menu?: {
// links to an external URL
url?: string;
// links to another page or action
route?: 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
}[]
}
Returns
null
Examples
Adding menus
Each item can be given a dropdown menu using the menu
property in the renderItem
callback. Menu items are typically links to other actions, but can also be external URLs.
- TypeScript
- JavaScript
await io.display.grid("Albums", {
data: albums,
idealColumnWidth: 180,
renderItem: row => ({
label: row.title,
description: row.artist,
image: {
url: row.imageUrl,
aspectRatio: 1,
},
menu: [
{
label: "Edit metadata",
route: `albums/edit`,
params: { id: row.id },
},
{
label: "Listen on Spotify",
url: `https://open.spotify.com/track/${spotifyId}`,
},
],
}),
});
await io.display.grid("Albums", {
data: albums,
idealColumnWidth: 180,
renderItem: row => ({
label: row.title,
description: row.artist,
image: {
url: row.imageUrl,
aspectRatio: 1,
},
menu: [
{
label: "Edit metadata",
route: `albums/edit`,
params: { id: row.id },
},
{
label: "Listen on Spotify",
url: `https://open.spotify.com/track/${spotifyId}`,
},
],
}),
});
Asynchronous data
tip
Best for situations like:
- Large data sets of thousands of records
- Fetching additional data from external APIs like Stripe
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 that Interval includes automatically,
like data caching and client-side searching and pagination. See the code sample below for how to implement
sorting, searching, and pagination in your custom getData
handler.
When in doubt, start with the synchronous data
property and use getData
when the situation requires it.
- TypeScript
- JavaScript
await io.display.grid("Albums", {
getData: async ({ queryTerm, offset, pageSize }) => {
const where = {
OR: [
{
artist: {
search: queryTerm,
},
},
{
album: {
search: queryTerm,
},
},
],
};
const data = await db.albums.find({
where,
skip: offset,
limit: pageSize,
});
const totalRecords = await db.albums.count({
where,
});
return {
data,
totalRecords,
};
},
idealColumnWidth: 180,
renderItem: row => ({
label: row.title,
description: row.artist,
image: {
url: row.imageUrl,
},
}),
});
await io.display.grid("Albums", {
getData: async ({ queryTerm, offset, pageSize }) => {
const where = {
OR: [
{
artist: {
search: queryTerm,
},
},
{
album: {
search: queryTerm,
},
},
],
};
const data = await db.albums.find({
where,
skip: offset,
limit: pageSize,
});
const totalRecords = await db.albums.count({
where,
});
return {
data,
totalRecords,
};
},
idealColumnWidth: 180,
renderItem: row => ({
label: row.title,
description: row.artist,
image: {
url: row.imageUrl,
},
}),
});
danger
Be sure to safely sanitize, escape, or prepare all values before passing them to any database queries.