FreeGridv0.8.1한국어

Row drag & trees

Drag rows to another parent or another grid, load tree children on expand, scroll two grids together.

Code for this demo

import { createGrid, type Row } from '@freegridjs/core';
import { separator, text, toggle, tr, type DemoContext } from '../ui';

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  // two grids side by side
  ctx.host.style.display = 'grid';
  ctx.host.style.gridTemplateColumns = 'repeat(auto-fit, minmax(300px, 1fr))';
  ctx.host.style.gap = '16px';
  const left = document.createElement('div');
  const right = document.createElement('div');
  for (const el of [left, right]) el.style.cssText = 'height: 100%; min-height: 0';
  ctx.host.style.gridAutoRows = '100%';
  ctx.host.append(left, right);

  const tasks: Row[] = [
    { id: 1, name: h('Website launch', '웹사이트 오픈'), parent: null, folder: true },
    { id: 2, name: h('Design', '디자인'), parent: 1, folder: true },
    { id: 3, name: h('Build', '개발'), parent: 1, folder: true },
    { id: 4, name: h('Logo', '로고'), parent: 2 },
    // children of this one come from the "server" on first expand
    { id: 5, name: h('Archive (loads on expand)', '보관함 (펼칠 때 불러옴)'), parent: null, folder: true, lazy: true },
  ];
  let nextId = 100;
  const columns = [{ field: 'name', header: h('Tasks', '작업'), flex: 1 }];

  const tree = createGrid(left, {
    columns, data: tasks, locale: lang, getRowId: r => String(r.id), groupDefaultExpanded: -1,
    rowDrag: {
      group: 'tasks',
      // only folders take children
      canDrop: ({ target, position }) => position !== 'inside' || !!target?.folder,
    },
    treeData: {
      getParentId: r => r.parent,
      setParentId: (row, parentId) => { row.parent = parentId; },
      hasChildren: r => !!r.lazy,
      // pretend server: answers after 600 ms
      loadChildren: row => new Promise<Row[]>(resolve => setTimeout(() => resolve([
        { id: nextId++, name: h('Old banner', '예전 배너'), parent: row.id },
        { id: nextId++, name: h('2025 report', '2025 보고서'), parent: row.id },
      ]), 600)),
    },
  });
  const backlog = createGrid(right, {
    columns: [{ field: 'name', header: h('Backlog', '백로그'), flex: 1 }],
    data: Array.from({ length: 30 }, (_, i) => ({ id: 200 + i, name: `${h('Idea', '아이디어')} ${i + 1}`, parent: null })),
    locale: lang, getRowId: r => String(r.id),
    rowDrag: { group: 'tasks' },
  });

  for (const [g, name] of [[tree, h('tasks', '작업')], [backlog, h('backlog', '백로그')]] as const) {
    g.on('rowDrop', e => ctx.log(`rowDrop (${name}) → "${e.row.name}" ${e.position} ${e.target ? `"${e.target.name}"` : h('end', '끝')}${e.fromOtherGrid ? h(' from the other grid', ' (다른 그리드에서)') : ''}`));
  }
  tree.on('childrenLoaded', e => ctx.log(`childrenLoaded → ${e.rows.length}`));

  text(ctx, h('Drag a row by its handle: onto the middle of a folder to move it inside, onto an edge to put it before or after. Drag between the two grids too.',
    '손잡이로 행을 끌어 보세요. 폴더 가운데에 놓으면 그 안으로, 위·아래 가장자리에 놓으면 앞이나 뒤로 갑니다. 두 그리드 사이로도 옮길 수 있습니다.'));
  separator(ctx);
  let unlink: (() => void) | null = null;
  toggle(ctx, h('Scroll both grids together', '두 그리드 함께 스크롤'), false, on => {
    if (on) unlink = tree.syncScroll(backlog, 'vertical');
    else unlink?.();
    ctx.log(`syncScroll: ${on}`);
  });
  return tree;
}