Introduction
Creating a cell template is the best way to customize data visualization and behaviour in ReactGrid. You can define your own one and then use it as other built-in cell types.
In this example, we will explain how to make it.
Let's imagine that you need to implement a brand new cell for displaying a country flag.
Expected functionality:
- In edit mode a cell allows you to type in text without any constraints
- When a cell is displayed, it should show a country flag based on the cell text
Let's get started!
1. Define flag cell interface
For this tutorial we are going to use the previous project - handling changes.
type attribute is necessary in any cell template, in our sample we will refer to this cell template by flag.
text attribute will store our text and it's also necessary.
export interface FlagCell extends Cell {
  type: "flag";
  text: string;
}2. Data holders
At the beginning we should update columns and rows displayed by the grid.
Cell type in row 1, 2 and 3 will be changed to flag at the end.
const [columns] = React.useState<Column[]>(() => [{ columnId: 0, width: 100 }]);
const [rows, setRows] = React.useState<Row<DefaultCellTypes | FlagCell>[]>(
  () => [
    {
      rowId: 0,
      height: 40,
      cells: [{ type: "header", text: "Flag" }],
    },
    {
      rowId: 1,
      height: 40,
      cells: [{ type: "text", text: "rus" }],
    },
    {
      rowId: 2,
      height: 40,
      cells: [{ type: "text", text: "usa" }],
    },
    {
      rowId: 3,
      height: 40,
      cells: [{ type: "text", text: "" }],
    },
  ]
);3. Creating necessary files
Create new file named FlagCellTemplate.tsx, add the same imports as in the listing below and make flag-cell-style.cssfile that
will contain some CSS styles (don't forget to import it into your project's file).
// FlagCellTemplate.tsx
import * as React from "react";
import {
  CellTemplate,
  Cell,
  Compatible,
  Uncertain,
  UncertainCompatible,
  isNavigationKey,
  getCellProperty,
  isAlphaNumericKey,
  keyCodes,
} from "@silevis/reactgrid";
import "./flag-cell-style.css";4. Creating FlagCellTemplate class
For all defined methods below it is necessary to display and interface the flag cell with internal ReactGrid model correctly.
- getCompatibleCellas a parameter gets an incoming cell and returns a compatible cell (with- textand- valueattribute required by- Compatibletype). In more complex examples- getCellPropertymay throw an exception if the required cell field is- undefined.
getCompatibleCell(uncertainCell: Uncertain<FlagCell>): Compatible<FlagCell> {
    const text = getCellProperty(uncertainCell, 'text', 'string');
    const value = parseFloat(text);
    return { ...uncertainCell, text, value };
}- handleKeyDownmethod handles- keyDownevent on this cell template. Here it just returns unchanged cell and enables edit mode when a user performed a click or pressed ENTER key.
handleKeyDown(
    cell: Compatible<FlagCell>,
    keyCode: number,
    ctrl: boolean,
    shift: boolean,
    alt: boolean
): { cell: Compatible<FlagCell>; enableEditMode: boolean } {
    if (!ctrl && !alt && isAlphaNumericKey(keyCode))
        return { cell, enableEditMode: true };
    return {
        cell,
        enableEditMode: keyCode === keyCodes.POINTER || keyCode === keyCodes.ENTER
    };
}- update- as we are not sure if an incoming cell has the same interface like- FlagCellso we mark it as- UncertainCompatible(the incoming cell has attributes provided by- Compatiblebut it can have other attributes like- datefrom- DateCell). In our case, we just copy- celland replace- textvalue.
update(cell: Compatible<FlagCell>, cellToMerge: UncertainCompatible<FlagCell>): Compatible<FlagCell> {
    return this.getCompatibleCell({ ...cell, text: cellToMerge.text });
}- rendermethod returns the content of a cell. In edit mode we will display- inputelement. The change which is made in the cell- inputis propagated outside ReactGrid by- onCellChangedfunction.
render(
    cell: Compatible<FlagCell>,
    isInEditMode: boolean,
    onCellChanged: (cell: Compatible<FlagCell>, commit: boolean) => void
): React.ReactNode {
  if (!isInEditMode) {
    const flagISO = cell.text.toLowerCase(); // ISO 3166-1, 2/3 letters
    const flagURL = `https://restcountries.eu/data/${flagISO}.svg`;
    const alternativeURL = `https://upload.wikimedia.org/wikipedia/commons/0/04/Nuvola_unknown_flag.svg`;
    return (
      <div
        className="rg-flag-wrapper"
        style={{ backgroundImage: `url(${flagURL}), url(${alternativeURL})` }}
      />
    );
  }
  return (
    <input
      ref={input => {
        input && input.focus();
      }}
      defaultValue={cell.text}
      onChange={e =>
        onCellChanged(
          this.getCompatibleCell({ ...cell, text: e.currentTarget.value }),
          false
        )
      }
      onCopy={e => e.stopPropagation()}
      onCut={e => e.stopPropagation()}
      onPaste={e => e.stopPropagation()}
      onPointerDown={e => e.stopPropagation()}
      onKeyDown={e => {
      if (isAlphaNumericKey(e.keyCode) || isNavigationKey(e.keyCode))
        e.stopPropagation();
      }}
    />
  );
}5. Styling
To set a flag as the background covering the div element, we created CSS rg-flag-wrapper class.
All of the cells have a class name created based on the cell type attribute. In our case, the cell class name will be rg-flag-cell.
As rg-cell is a flex element we center its content with justify-content: center; attribute.
.rg-flag-cell {
  justify-content: center;
}
 
.rg-flag-wrapper {
  width: 50%;
  height: 80%;
  background-size: cover;
  border: 1px solid #cccccc;
  background-position: center center;
  background-repeat: no-repeat;
}6. Finishing
Go back to index.tsx file and add customCellTemplates property as shown below and also change type: "text" to type: "flag":
// ...
const [rows, setRows] = React.useState<Row<DefaultCellTypes | FlagCell>[]>(
  () => [
    {
      rowId: 0,
      height: 40,
      cells: [{ type: "header", text: "Flag" }],
    },
    {
      rowId: 1,
      height: 40,
      cells: [{ type: "flag", text: "rus" }], // highlight-line
    },
    {
      rowId: 2,
      height: 40,
      cells: [{ type: "flag", text: "usa" }], // highlight-line
    },
    {
      rowId: 3,
      height: 40,
      cells: [{ type: "flag", text: "" }], // highlight-line
    },
  ]
);
 
return (
  <ReactGrid
    rows={rows}
    columns={columns}
    onCellsChanged={handleChanges}
    customCellTemplates={{ flag: new FlagCellTemplate() }} //highlight-line
  />
);Result
Code
interface Flag { isoCode: string; } const getFlags = (): Flag[] => [ { isoCode: "pl" }, { isoCode: "de" }, { isoCode: "mx" }, { isoCode: "" } ]; const getColumns = (): Column[] => [{ columnId: "flag", width: 150 }]; const headerRow: Row = { rowId: "header", height: 40, cells: [{ type: "header", text: "Flags" }] }; const getRows = (flags: Flag[]): Row<DefaultCellTypes | FlagCell>[] => [ headerRow, ...flags.map<Row<DefaultCellTypes | FlagCell>>((flag, idx) => ({ rowId: idx, height: 60, cells: [{ type: "flag", text: flag.isoCode }] })) ]; function App() { const [flags] = React.useState<Flag[]>(getFlags()); const rows = getRows(flags); const columns = getColumns(); return ( <ReactGrid rows={rows} columns={columns} customCellTemplates={{ flag: new FlagCellTemplate() }} /> ); } render(<App />, document.getElementById("root"));
Preview