FreeGridv1.15.1한국어

Buttons, links and images in cells

Built-in button, link and image cells: clicks do not select the row or start an edit, links take {field} templates and refuse javascript: URLs, and renderers can clean up with destroy.

Live demoButtons, links and images

Open and delete buttons, profile links and photos.

Code for this demo
import { createGrid, type ColumnDef, type Row } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';

export default function mount(ctx: DemoContext) {
  const h = (en: string, ko: string) => tr(ctx.lang, { en, ko });
  const photo = (i: number) => `data:image/svg+xml;utf8,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="hsl(${(i * 47) % 360} 70% 55%)"/><text x="16" y="21" font-size="14" text-anchor="middle" fill="white" font-family="sans-serif">${String.fromCharCode(65 + (i % 26))}</text></svg>`)}`;
  const data = makeRows(200).map((r, i) => ({ ...r, photo: photo(i), archived: i % 5 === 0, locked: i % 11 === 0 }));
  const grid = createGrid(ctx.host, {
    columns: [
      { field: 'photo', header: '', width: 52, image: { width: 28, height: 28, round: true, alt: p => p.row.name } },
      { field: 'name', header: h('Name', '이름'), width: 170, link: { href: '#/people/{id}', onClick: p => ctx.log(`open profile #${p.row.id}`) } },
      { field: 'email', header: h('Email', '이메일'), width: 240, link: { href: p => `mailto:${p.value}` } },
      { id: 'open', header: '', width: 90, button: { label: h('Open', '열기'), onClick: p => ctx.log(`open order #${p.row.id}`) } },
      { id: 'archive', header: '', width: 100, button: {
        label: p => (p.row.archived ? h('Restore', '복원') : h('Archive', '보관')),
        disabled: p => p.row.locked,
        onClick: p => { grid.applyTransaction({ update: [{ ...p.row, archived: !p.row.archived }] }); ctx.log(`#${p.row.id} archived = ${!p.row.archived}`); },
      } },
    ],
    data, locale: ctx.lang, getRowId: r => String(r.id), rowSelection: 'multiple',
  });
  text(ctx, h('Buttons and links act on their own: the row is not selected and no editor opens. Locked rows have a greyed-out button.',
    '버튼과 링크는 따로 동작해 행이 선택되거나 편집기가 열리지 않습니다. 잠긴 행의 버튼은 흐리게 보입니다.'));
  return grid;
}

Three column properties put a clickable element in every cell without writing a renderer. Their clicks belong to them: the focus does not move, the row is not selected or checked, a range does not start and a double-click does not open an editor.

Buttons

const columns = [
  { id: 'open', header: '', width: 80, pinned: 'right',
    button: { label: 'Open', onClick: p => openOrder(p.row.id) } },
  { id: 'remove', header: '', width: 80,
    button: {
      label: p => (p.row.archived ? 'Restore' : 'Archive'),     // text per row
      disabled: p => p.row.locked,                               // greyed out, cannot be clicked
      onClick: p => grid.applyTransaction({ update: [{ ...p.row, archived: !p.row.archived }] }),
    } },
];
NameTypeDescription
labelstring | (params) => stringThe button text (default: the cell value).
onClick(params) => voidCalled with { value, formatted, row, rowIndex, column }.
disabled(params) => booleanGrey the button out for some rows.
const columns = [
  // a template: {field} is replaced by the row's value (URL-encoded)
  { field: 'name', link: { href: '/customers/{id}?tab=orders' } },
  // a function, a new tab, a fixed label
  { field: 'site', link: { href: p => p.value, target: '_blank', label: () => 'Visit' } },
  { field: 'email', link: { href: p => 'mailto:' + p.value } },
  // handled in your app (a router) instead of a page load
  { field: 'orderNo', link: { href: '/orders/{orderNo}', onClick: (p, e) => router.push('/orders/' + p.row.orderNo) } },
];
NameTypeDescription
hrefstring | (params) => stringA template with {field} placeholders, or a function. Only http(s), mailto:, tel: and relative links (/, ./, #, ?) are made; anything else (javascript:) leaves the text without a link.
targetstringWhere to open it; '_blank' also gets rel="noopener noreferrer".
label(params) => stringThe link text (default: the formatted value).
onClick(params, event) => voidHandle the click yourself; the browser does not follow the link.

Images

{ field: 'photoUrl', header: 'Photo', width: 60, image: { width: 32, height: 32, round: true, alt: p => p.row.name } }
NameTypeDescription
width / heightnumberSize in px (height defaults to the row height less 8).
roundbooleanA circle (profile pictures).
alt(params) => stringText for screen readers.

Images load lazily as rows scroll into view. Only http(s), data:image/, blob: and relative addresses are shown. Excel export can embed them with images: true (see Excel: pictures, notes, protection).

Your own elements: cleaning up

For anything else, a renderer returns HTML or an element. If it mounts something that needs cleaning up (a React root, a chart, a timer), return { el, destroy }: destroy runs before the cell is drawn again and when the grid is destroyed.

import { createRoot } from 'react-dom/client';

{ field: 'trend', renderer: p => {
  const el = document.createElement('div');
  const root = createRoot(el);
  root.render(<TrendChart values={p.value} />);
  return { el, destroy: () => root.unmount() };
} }

Questions about this page

How do I add a button to each row of a grid?

Add a column with button: { label: 'Open', onClick: p => open(p.row) }. The click calls onClick with the cell and does not select the row or open an editor.