Reusable ag-grid component for LINZ / Toitū te whenua.
- ag-grid-community based grid with custom popover components implemented using a modified react-menu.
- Default components
- Text input
- Text area
- Drop-down
- Multi-select
- Multi-select-grid
- Bearing/Bearing Correction
- Popover message
- Custom form
- Context menu
Please note this requires React >=17, ag-grid-community >=28, and SASS.
with npm
npm install @linzjs/step-ag-grid
or with Yarn
yarn add @linzjs/step-ag-grid
npm run storybook
Storybook demo deployed at: https://master--633cd0dc2fe91d7df3ed32e4.chromatic.com/
Check src\stories
for more usage examples
import { useMemo } from "react";
import "@linzjs/lui/dist/fonts";
import "@linzjs/lui/dist/scss/base.scss";
import {
ColDefT,
GridCell,
GridCellFiller,
GridContextProvider,
GridContextMenuComponentProps,
GridPopoverEditDropDown,
GridPopoverMessage,
GridUpdatingContextProvider,
GridWrapper,
GridFilters,
GridFilterQuick,
GridFilterButtons
} from "@linzjs/step-ag-grid";
// Only required for LINZ themes otherwise import the default theme from ag-grid
import "@linzjs/step-ag-grid/dist/GridTheme.scss";
import "@linzjs/step-ag-grid/dist/index.css";
import { GridFilterDownloadCsvButton } from "./GridFilterDownloadCsvButton";
const GridDemo = () => {
interface ITestRow {
id: number;
name: number;
position: string;
}
const columnDefs: ColDefT<ITestRow>[] = useMemo(
() => [
GridCell({
field: "id",
headerName: "Id",
export: false,
}),
// This is the flex column that will expand to fit
GridCell<ITestRow, string>({
field: "name",
headerName: "Name",
flex: 1,
cellRendererParams: {
warning: ({ value }) => value === "Tester" && "Testers are testing",
info: ({ value }) => value === "Developer" && "Developers are awesome",
},
}),
GridPopoverEditDropDown(
{
field: "position",
headerName: "Position",
},
{
multiEdit: false,
editorParams: {
options: ["Architect", "Developer", "Product Owner", "Scrum Master", "Tester", MenuSeparator, "(other)"],
},
},
),
GridPopoverMessage(
{
headerName: "Popout message",
cellRenderer: () => <>Click me!</>,
},
{
multiEdit: true,
editorParams: {
message: async ({selectedRows}) => {
return `There are ${selectedRows.length} row(s) selected`;
},
},
},
),
// If your flex column gets hidden this will become active
GridCellFiller(),
],
[],
);
const ContextMenu = ({ selectedRows, colDef, close }: GridContextMenuComponentProps<ITestRow>): ReactElement => {
const onClick = useCallback(() => {
selectedRows.forEach((row) => {
switch (colDef.field) {
case "name":
row.name = "";
break;
case "distance":
row.distance = null;
break;
}
});
close();
}, [close, colDef.field, selectedRows]);
return (
<>
<button onClick={onClick}>Button - Clear cell</button>
<MenuItem onClick={onClick}>Menu Item - Clear cell</MenuItem>
</>
);
};
const rowData: ITestRow[] = useMemo(
() => [
{ id: 1000, name: "Tom", position: "Tester" },
{ id: 1001, name: "Sue", position: "Developer" },
],
[],
);
return (
<GridUpdatingContextProvider>
<GridContextProvider>
<GridWrapper>
<GridFilters>
<GridFilterQuick/>
<GridFilterButtons<ITestRow>
options={[
{
label: "All",
},
{
label: "Developers",
filter: (row) => row.position === "Developer",
},
{
label: "Testers",
filter: (row) => row.position === "Tester",
},
]}
/>
<GridFilterColumnsToggle/>
<GridFilterDownloadCsvButton fileName={"exportFile"}/>
</GridFilters>
<Grid selectable={true}
columnDefs={columnDefs}
rowData={rowData}
contextMenu={contextMenu}
contextMenuSelectRow={false}
onContentSize={({ width }) => setPanelSize(width)} />
</GridWrapper>
</GridContextProvider>
</GridUpdatingContextProvider>
);
};
If you are editing a cell and tab out of the cell, the grid will edit the next editable cell.
At this point you can send the change to the back-end immediately and then wait for an update response
OR
you could cache the required change, update then cell locally, and then wait for the callback
<Grid onCellEditingComplete={fn}/>
which will get invoked when the grid cannot find any
more editable cells on the grid row, which will speed up editing.
Grid uses <Grid sizeColumns="auto"/>
which sizes by cell content by default.
To ignore cell content use "fit", to disable use "none".
If you are within a resizable window/dialog/container there is a callback parameter
<Grid onContentSize={({ width }) => setPanelSize(width)}/>
to receive the recommended container width.
CSV download relies on column valueFormatters vs ag-grid's default valueGetter implementation.
If you use a customRenderer for a column be sure to include a valueFormatter.
To disable this behaviour pass undefined to processCellCallback.
<GridFilterDownloadCsvButton processCellCallback={undefined}/>
To exclude a column from CSV download add export: false
to the GridCell definition.
The following testing calls can be imported from step-ag-grid:
- findRow
- queryRow
- selectRow
- deselectRow
- findCell
- selectCell
- editCell
- findOpenMenu
- validateMenuOptions
- queryMenuOption
- findMenuOption
- clickMenuOption
- openAndClickMenuOption
- getMultiSelectOptions
- findMultiSelectOption
- clickMultiSelectOption
- typeOnlyInput
- typeInputByLabel
- typeInputByPlaceholder
- typeOtherInput
- typeOtherTextArea
- closeMenu
- findActionButton
- clickActionButton
import { render, screen } from "@testing-library/react";
import { waitFor } from "@testing-library/react";
import { findRow, GridUpdatingContextProvider, openAndClickMenuOption } from "@linzjs/step-ag-grid";
const TestComponent = (): JSX.Element => {
return (
<GridUpdatingContextProvider>
<MyGrid />
</GridUpdatingContextProvider>
);
};
test("click Delete menu option removes row from the table", async () => {
await render(<TestComponent />);
await screen.findByText("My component header");
expect((await findRow(12345)).getAttribute("row-index")).toBe("1");
await openAndClickMenuOption(12345, "actions", "Delete");
await waitFor(async () => expect((await queryRow(12345)).not.toBeDefined()));
});
If your grid has a data-testid a global will be exposed in window with the helper scrollRowIntoViewById. This will throw an exception if the row id is not found.
window.__stepAgGrid.grids[dataTestId].scrollRowIntoViewById("1000")