FreeGridv0.7.0한국어

Grouping & trees

Group rows with totals, nest rows as a tree, open detail panels, drag rows into order.

Code for this demo

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

type Mode = 'group' | 'tree' | 'detail' | 'drag';

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  const money = { type: 'number', format: '#,##0', width: 120, aggFunc: 'sum' } as const;
  const sales: ColumnDef[] = [
    { field: 'country', header: h('Country', '국가'), width: 150 },
    { field: 'company', header: h('Company', '회사'), width: 140 },
    { field: 'name', header: h('Name', '이름'), width: 170 },
    { field: 'q1', header: h('Q1 revenue', '1분기 매출'), ...money },
    { field: 'q2', header: h('Q2 revenue', '2분기 매출'), ...money },
    { field: 'total', header: h('Total', '합계'), ...money, width: 130 },
    { field: 'id', header: h('People', '인원'), type: 'number', width: 90, aggFunc: 'count' },
  ];

  // An org chart: departments contain teams, teams contain people.
  const org: Row[] = [
    { id: 'hq', parent: null, name: h('Head office', '본사'), role: '', budget: 1200 },
    { id: 'dev', parent: 'hq', name: h('Development', '개발본부'), role: '', budget: 640 },
    { id: 'web', parent: 'dev', name: h('Web team', '웹팀'), role: '', budget: 260 },
    { id: 'p1', parent: 'web', name: '김민준', role: h('Lead', '팀장'), budget: 0 },
    { id: 'p2', parent: 'web', name: '이서연', role: h('Engineer', '개발자'), budget: 0 },
    { id: 'app', parent: 'dev', name: h('App team', '앱팀'), role: '', budget: 380 },
    { id: 'p3', parent: 'app', name: '박지호', role: h('Engineer', '개발자'), budget: 0 },
    { id: 'biz', parent: 'hq', name: h('Business', '사업본부'), role: '', budget: 560 },
    { id: 'p4', parent: 'biz', name: '최유나', role: h('Manager', '매니저'), budget: 0 },
    { id: 'p5', parent: 'biz', name: '정하준', role: h('Sales', '영업'), budget: 0 },
  ];

  let grid: GridApi | null = null;
  const show = (mode: Mode) => {
    grid?.destroy();
    let options: GridOptions;
    if (mode === 'tree') {
      options = {
        columns: [
          { field: 'name', header: h('Organization', '조직'), width: 260 },
          { field: 'role', header: h('Role', '직책'), width: 140 },
          { field: 'budget', header: h('Budget (M)', '예산(백만)'), type: 'number', format: '#,##0', width: 140 },
        ],
        data: org.map(r => ({ ...r })),
        getRowId: r => r.id,
        treeData: { getParentId: r => r.parent },
        groupDefaultExpanded: 1,
      };
    } else if (mode === 'detail') {
      options = {
        columns: sales.map(({ aggFunc, ...c }) => c),
        data: makeRows(200),
        detailHeight: 180,
        detailRenderer: ({ row }) => {
          // a small grid inside the panel: the person's quarters
          const el = document.createElement('div');
          el.style.height = '158px';
          createGrid(el, {
            columns: [
              { field: 'quarter', header: h('Quarter', '분기'), width: 120 },
              { field: 'revenue', header: h('Revenue', '매출'), type: 'number', format: '#,##0', width: 160 },
            ],
            data: ['q1', 'q2', 'q3', 'q4'].map((q, i) => ({ quarter: `${i + 1}Q`, revenue: row[q] })),
            locale: lang,
            rowHeight: 26,
          });
          return el;
        },
      };
    } else if (mode === 'drag') {
      options = { columns: sales.slice(0, 4).map(({ aggFunc, ...c }) => c), data: makeRows(30), rowDrag: true };
    } else {
      options = { columns: sales, data: makeRows(1000), groupBy: ['country'], groupPanel: true };
    }
    grid = createGrid(ctx.host, { locale: lang, theme: 'board', ...options });
    grid.on('groupToggle', e => ctx.log(`groupToggle → ${e.key}: ${e.expanded}`));
    grid.on('groupChange', e => ctx.log(`groupChange → [${e.groupBy.join(', ')}]`));
    grid.on('detailToggle', e => ctx.log(`detailToggle → ${e.row.name}: ${e.expanded}`));
    grid.on('rowDragEnd', e => ctx.log(`rowDragEnd → ${e.row.name}: ${e.fromIndex} → ${e.toIndex}`));
  };

  text(ctx, h('Drag a column header into the bar above the grid to add a grouping level.',
    '열 머리글을 그리드 위 막대로 끌어오면 묶음 단계가 늘어납니다.'));
  separator(ctx);
  button(ctx, h('Row grouping', '행 그룹핑'), () => show('group'));
  button(ctx, h('Tree data', '트리 데이터'), () => show('tree'));
  button(ctx, h('Master / detail', '마스터/디테일'), () => show('detail'));
  button(ctx, h('Row drag', '행 드래그'), () => show('drag'));
  separator(ctx);
  button(ctx, h('Expand all', '모두 펼치기'), () => grid?.expandAll());
  button(ctx, h('Collapse all', '모두 접기'), () => grid?.collapseAll());
  button(ctx, h('Group by country, then company', '국가 → 회사로 묶기'), () => grid?.setGroupBy(['country', 'company']));
  show('group');
  return grid;
}