# Grid API

> The grid's handle for actions that are not state, such as scrolling to a row, focusing a cell, saving a layout or patching live rows.

Most of the grid is driven by props: rows, columns, sort, filters. Some things are actions rather than state, with no resting value a prop could hold: scroll to this row, focus that cell, download the current view, save the layout. For those the grid hands you one object, the grid API, through `onReady`.

Each button calls one API method. Resize or move a column, then save the layout, change it again and restore it.

Example: Calling the grid API (grid-api.svelte)

```svelte
<script lang="ts">
  import { Grid, type ColumnDef, type GridApi, type GridState } from 'bo-grid';
  import { quotes } from './data';

  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', width: 100 },
    { type: 'text', key: 'name', header: 'Name', width: 160 },
    { type: 'price', key: 'price', header: 'Price' },
    { type: 'percent', key: 'change', header: 'Chg %' },
    { type: 'volume', key: 'volume', header: 'Volume', flex: 1 },
  ];
  let rows = $state(quotes());
  let api = $state<GridApi>();
  let saved = $state<GridState | null>(null);
</script>

<p style="margin: 0 0 10px; display: flex; flex-wrap: wrap; gap: 6px">
  <button onclick={() => api?.scrollToRow(11, 'center')}>Scroll to TCB</button>
  <button onclick={() => api?.focusCell(3, 'price')}>Focus HPG price</button>
  <button onclick={() => api?.autosizeColumns()}>Autosize</button>
  <button onclick={() => (saved = api?.getState() ?? null)}>Save layout</button>
  <button disabled={!saved} onclick={() => saved && api?.applyState(saved)}>Restore layout</button>
</p>
<Grid {rows} {columns} height={240} columnMenu onReady={(a) => (api = a)} />
```

data.ts:

```ts
// Small, fixed data for the docs' examples: every example reads the same
// symbols, so a reader compares features, not datasets.
// A type, not an interface: it then fits GridRow's index signature.
export type Quote = {
  id: number;
  symbol: string;
  name: string;
  sector: string;
  price: number;
  change: number;
  volume: number;
};

const BASE: Omit<Quote, 'id'>[] = [
  { symbol: 'VNM', name: 'Vinamilk', sector: 'Consumer', price: 68.5, change: 1.2, volume: 1_204_000 },
  { symbol: 'FPT', name: 'FPT Corp', sector: 'Technology', price: 132, change: -0.4, volume: 860_500 },
  { symbol: 'HPG', name: 'Hoa Phat', sector: 'Materials', price: 27.35, change: 0.74, volume: 3_412_900 },
  { symbol: 'VCB', name: 'Vietcombank', sector: 'Financials', price: 91.5, change: 0.55, volume: 1_986_200 },
  { symbol: 'MWG', name: 'Mobile World', sector: 'Consumer', price: 51.4, change: -1.1, volume: 2_250_400 },
  { symbol: 'SSI', name: 'SSI Securities', sector: 'Financials', price: 33.3, change: -0.45, volume: 4_104_700 },
  { symbol: 'VIC', name: 'Vingroup', sector: 'Real estate', price: 44.15, change: -0.11, volume: 1_530_100 },
  { symbol: 'GAS', name: 'PV Gas', sector: 'Energy', price: 71.8, change: 0.3, volume: 645_800 },
  { symbol: 'MSN', name: 'Masan', sector: 'Consumer', price: 76.2, change: 2.05, volume: 1_120_300 },
  { symbol: 'TCB', name: 'Techcombank', sector: 'Financials', price: 23.9, change: 0.84, volume: 5_870_000 },
  { symbol: 'VHM', name: 'Vinhomes', sector: 'Real estate', price: 41.6, change: -0.72, volume: 2_904_600 },
  { symbol: 'POW', name: 'PV Power', sector: 'Energy', price: 12.25, change: 1.66, volume: 6_315_200 },
];

/** A fresh copy of the quotes, so one example's edits never reach another. */
export function quotes(): Quote[] {
  return BASE.map((q, i) => ({ id: i + 1, ...q }));
}
```

## Getting the handle

`onReady` is called once, when the grid mounts, with the API object:

```svelte
<script lang="ts">
  import { Grid, type GridApi } from 'bo-grid';
  let api = $state<GridApi>();
</script>

<button onclick={() => api?.scrollToRow('ORD-1042', 'center')}>Find order</button>
<Grid {rows} {columns} height={480} onReady={(a) => (api = a)} />
```

In React and Vue the same object is the component's ref (`ref.current` in React, the template ref's `api` in Vue); see [React, Vue & more](https://bonguynvan.github.io/bo-grid/docs/frameworks/).

## Methods

| Method | What it does |
| --- | --- |
| `scrollToRow(id, align?)` | Scroll a row into view; `align` is `'nearest'` (default), `'start'`, `'center'` or `'end'` |
| `focusCell(rowId, columnKey)` | Focus and select one cell |
| `getFocusedCell()` | The focused `{ row, column }`, or `null` |
| `getSelectedRows()` | The ticked rows (with `rowSelection`), in view order |
| `autosizeColumns(keys?)` | Fit columns to their content, as the column menu's **Autosize** does |
| `exportCSV(filename?)` | Download the current view, after filters and sort, as CSV |
| `getState()` / `applyState(state)` | Save and restore the user's layout (below) |
| `patchRows(patches)` | Write live values into rows in place; see [Live updates](https://bonguynvan.github.io/bo-grid/docs/realtime/) |
| `refresh()` | Re-run sort and filters against values changed in place |
| `flashCells(options?)` | Flash cells on demand; see [Cell flashing](https://bonguynvan.github.io/bo-grid/docs/flashing/) |
| `pinRow(id, pinned?)` / `getPinnedRowIds()` | Pin rows above the scroll from code |
| `openFind(query?)` | Open the find bar (with `findBar`) |

Methods that look a row up by id (`scrollToRow`, `focusCell`) return `false` when the row is not in the current view: filtered out, on another page, or in a grid backed by a server `source`.

## Saving a layout

`getState()` returns the user's layout as one plain, JSON-safe object: column order, widths, hidden columns, pinned columns, pinned rows, sorts and filters. Keep it wherever you keep user settings, per user or per workspace, and give it back with `applyState(saved)`:

```ts
const saved = api.getState();
localStorage.setItem('orders-layout', JSON.stringify(saved));

// later
api.applyState(JSON.parse(localStorage.getItem('orders-layout') ?? 'null'));
```

`applyState` fits the saved layout onto the columns as they are now:

- Entries for columns that no longer exist are dropped.
- A new column lands where it is declared, after the nearest column declared before it.
- Malformed fields are ignored rather than thrown on.
- A state from another `version` of the format is refused whole (`applyState` returns `false`), because half a layout is worse than none.

The same rules are exported as `reconcileState(saved, columnKeys)`, for a server that wants to apply them itself. For the simple case, `persistKey` saves the layout in the browser's local storage with no code.

## Also exported

Besides `Grid` and its types, the package exports helpers you can use on their own:

| Group | Exports |
| --- | --- |
| Components | `Grid`, `Sparkline` |
| Formatting | `fmtPrice`, `fmtPercent`, `fmtVolume`, `fmtDate`, `fmtCurrency`, `relativeTime` |
| Data | `aggregate`, `pivot`, `sortRows`, `heatColor`, `resolveColumns`, `buildMergePlan`, `colSpanRow` |
| Export and import | `toCSV`, `exportCSV`, `exportXLSX`, `parseCSV`, `parseCSVMatrix`, `parseTSV`, `parseJSON`, `parseRows`, `rowsFromObjects`, `toHTMLTable`, `printTable`, `escapeHTML` |
| Server data | `createArraySource` |
| Theming and text | `themeVars`, `themePresets` and the preset token maps, `DEFAULT_LABELS`, `resolveLabels` |
| Layout state | `reconcileState`, `GRID_STATE_VERSION` |
| Canvas | `drawCandles`, `setupHiDpiCanvas` (draw candlesticks on a canvas of your own) |

Realtime and trading helpers live in their own entries, `bo-grid/realtime`, `bo-grid/trading` and `bo-grid/ladder`. Every prop and method, with its type, is in the [Props reference](https://bonguynvan.github.io/bo-grid/docs/reference/).

## Reference

- `onReady`: `(api: GridApi) => void` — Called once, when the grid mounts, with a handle for imperative actions: `scrollToRow`, `focusCell`, `patchRows`, `getSelectedRows`, `autosizeColumns`, `exportCSV`, `getState` / `applyState` and more.

  ```svelte
  <Grid {rows} {columns} height={480} onReady={(a) => (api = a)} />
  ```

- `persistKey`: `string` (default `undefined`) — Remember the user's layout (column order, widths, hidden and pinned columns, pinned rows) in localStorage under this key.

- `scrollToRow(key: string | number, align?: ScrollAlign): boolean` — Scroll a row into view by its id; `align` is `'nearest'` unless given. Returns false when the row is not in the current view (filtered out, not loaded, or unknown).

  ```ts
  api.scrollToRow(order.id, 'center');
  ```

- `focusCell(rowKey: string | number, columnKey: string): boolean` — Focus (and select) one cell by row id and column key. Returns false when the row or column is not currently visible.

  ```ts
  if (!api.focusCell(row.id, 'qty')) api.scrollToRow(row.id);
  ```

- `getFocusedCell(): { row: GridRow; column: ColumnDef } | null` — The focused cell's row and column, or null when no data cell has focus (nothing focused yet, or the focus is on a group row). For hotkeys that act on the row the user is on.

  ```ts
  const cell = api.getFocusedCell();
  if (cell) openTicket(cell.row);
  ```

- `getSelectedRows(): GridRow[]` — Ticked rows (needs `rowSelection`), in view order.

  ```ts
  const ids = api.getSelectedRows().map((row) => row.id);
  ```

- `autosizeColumns(keys?: string[]): void` — Fit columns to their content, as the column menu's Autosize does. All resizable columns when `keys` is omitted. The other columns keep their widths (flex ones are held where they are).

  ```ts
  api.autosizeColumns(['sym', 'name']);
  ```

- `exportCSV(filename?: string): Promise<void>` — Download the current view (after filter and sort) as CSV, named `export.csv` unless `filename` is given.

  ```ts
  await api.exportCSV(`orders-${new Date().toISOString().slice(0, 10)}.csv`);
  ```

- `getState(): GridState` — Snapshot of the user's layout: order, widths, hidden, pins, sorts, filters. A plain object, safe to `JSON.stringify` and store.

  ```ts
  localStorage.setItem('orders-layout', JSON.stringify(api.getState()));
  ```

- `applyState(state: unknown): boolean` — Restore a snapshot, reconciled against the columns as they are now. Returns false when the state is unusable (foreign version, not an object).

  ```ts
  const saved = localStorage.getItem('orders-layout');
  if (saved) api.applyState(JSON.parse(saved));
  ```

- `patchRows(patches: Iterable<readonly [string | number, Record<string, unknown>]>): number` — Realtime fast path: write `[rowId, fields]` patches into the rows in place and repaint only the rendered rows they change. Rows may be plain objects (no $state proxy) — far cheaper per write in a busy feed. Feed it a tick stream: `createTickStream({ apply: (b) => api.patchRows(b) })`. Sort and filter catch up on the next view change, as for any live data (or at once, with `liveSort`). A patch's id is the row's `getRowId` value (`row.id` unless set). Returns how many rows changed.

  ```ts
  api.patchRows([
    [1, { last: 68.6, vol: 1_204_300 }],
    [2, { last: 121.1 }],
  ]);
  ```

- `refresh(): void` — Re-run filter and sort against the rows' current values. Values that change in place (a feed) never re-sort the view by themselves — that would mean re-sorting on every tick — so call this when the order should catch up, e.g. every second on a "top movers" board (or use `liveSort`).

  ```ts
  const timer = setInterval(() => api.refresh(), 1000);
  ```

- `flashCells(opts?: FlashCellsOptions): number` — Flash cells on demand — a fill, an alert, a row to notice — with the tick animation, whether or not their values changed. Only rows on screen flash. Value cells flash (numbers, text); cells that draw their own content (badges, sparklines, components) do not. Returns how many rows it flashed.

  ```ts
  api.flashCells({ rows: [fill.orderId], dir: fill.side === 'buy' ? 'up' : 'down', ms: 800 });
  ```

- `pinRow(key: string | number, pinned?: boolean): boolean` — Pin a row above the scroll (or unpin it with `pinned: false`), as the row menu does under `rowPinning`; `pinned` is true unless given. The row stays in the body too. Returns false when no row has that id.

  ```ts
  api.pinRow(row.id);
  ```

- `getPinnedRowIds(): (string | number)[]` — Ids of the rows pinned at runtime, in pin order.

  ```ts
  for (const id of api.getPinnedRowIds()) api.pinRow(id, false);
  ```

- `openFind(query?: string): void` — Open the find bar (`findBar`), optionally searching for `query` at once.

  ```ts
  api.openFind('VNM');
  ```

---
Page: https://bonguynvan.github.io/bo-grid/docs/grid-api/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
