Skip to main content

io.display.grid

Displays data in a grid layout.

info

Added in v0.36.0.

Grid items can include a title, 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

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 => ({
title: row.album,
description: row.artist,
image: {
url: row.imageUrl,
aspectRatio: 1,
},
}),
});
interval.com

Props

data

Optional

T[]

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

defaultPageSize

Optional

number

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

getData

Optional

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

An async function that receives the current grid 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 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

Secondary label providing an additional description of the data.

idealColumnWidth

Optional

number

Used to control grid item sizing. Interval will calculate a column width that is as close to this number as possible while factoring in gutter size and window width.

isFilterable

Optional

boolean

Whether to show a text input alongside the grid for filtering and searching through items. Interval automatically handles filtering when using `data`, but you must implement filtering yourself when using `getData`. See the 'Asynchronous data' example for a code sample. Defaults to `true`.

renderItem

Optional

(row: T) => object

Function that receives the row as argument and returns an object to control various components of the display output. See the function definition below for available properties.
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.

await io.display.grid("Albums", {
data: albums,
idealColumnWidth: 180,
renderItem: row => ({
title: 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}`,
},
],
}),
});
interval.com

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.

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 => ({
title: 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.

Did this section clearly explain what you wanted to learn?