FreeGridv1.15.1English

행 드래그

행을 끌어 순서를 바꾸고, 트리에서 다른 부모로, 다른 그리드로 옮깁니다. canDrop 규칙, 실행 취소, 순서 저장용 이벤트가 있습니다.

실행 예제행 드래그

행을 다른 부모나 다른 그리드로 옮기고, 펼칠 때 자식을 불러오고, 두 그리드를 함께 스크롤합니다.

이 데모의 코드
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;
}

rowDrag: true를 켜면 손잡이 컬럼이 생겨 우선순위나 작업 순서처럼 행 순서를 직접 바꿀 수 있습니다. 손잡이로 행을 끌어 두 행 사이에 놓으면 원본 데이터 순서가 바뀌고, Ctrl+Z로 되돌릴 수 있으며, rowDragEnd 이벤트가 이전 위치와 새 위치를 알려 주므로 순서를 저장하면 됩니다. 정렬·필터·그룹이 걸려 있으면 화면 순서와 데이터 순서가 달라서 끌기가 잠시 꺼집니다.

const grid = createGrid(el, { columns, data, getRowId: r => r.id, rowDrag: true });

grid.on('rowDragEnd', e => saveOrder(e.row, e.toIndex));   // { row, fromIndex, toIndex }

// the same from code (undoable); false while sorted, filtered or grouped
grid.moveRow(rows[5], 0);
grid.dropRow(rows[5], rows[2], 'after');   // 'before' | 'after' | 'inside' (tree data)

다른 부모, 다른 그리드로

트리 데이터에서 treeData.setParentId를 주면 행을 다른 부모 아래로 옮길 수 있습니다. 행의 위쪽이나 아래쪽 가장자리에 놓으면 형제가 되고, 가운데에 놓으면 자식이 됩니다. rowDrag에 같은 group을 준 그리드끼리는 작업 목록과 백로그처럼 행을 주고받을 수 있고, 행을 받은 그리드에서 rowDrop이 발생합니다. canDrop으로 폴더가 아닌 행 안에 놓는 것처럼 놓을 수 없는 곳을 막습니다. 두 그리드를 함께 스크롤하려면 syncScroll()을 쓰세요. 다른 그리드로 복사하기, 복사본에 새 id 주기, 놓기 규칙은 그리드 간 행 드래그에 있습니다.

// drag rows to another parent, and between two grids
const files = createGrid(left, {
  columns, data: roots, getRowId: r => r.id,
  rowDrag: { group: 'files', canDrop: ({ target, position }) => position !== 'inside' || target?.folder },
  treeData: {
    getParentId: r => r.parent,
    setParentId: (row, parentId) => { row.parent = parentId; },   // needed to move rows to another parent
  },
});
// another grid in the same group: rows can be dragged between them
const archive = createGrid(right, { columns, data: [], getRowId: r => r.id, rowDrag: { group: 'files' } });
archive.on('rowDrop', e => save(e.row, e.fromOtherGrid));

// keep two grids scrolled together
const unlink = files.syncScroll(archive, 'vertical');

옵션, 메서드, 이벤트

이름타입설명
rowDragboolean | { group?, canDrop? }행을 끄는 손잡이 컬럼. 정렬·필터·그룹이 걸려 있는 동안에는 잠시 꺼집니다.
rowDrag.groupstringgroup이 같은 그리드끼리 행을 주고받습니다.
rowDrag.canDrop({ row, target, position, fromOtherGrid })booleanfalse를 돌려주면 그곳에 놓을 수 없습니다. position은 'before', 'after', 트리에서는 'inside'.
moveRow(row, toIndex) / dropRow(row, target, position?)boolean코드로 행을 옮깁니다(실행 취소 가능). 거부되거나 정렬·필터·그룹 중이면 false.
syncScroll(otherGrid, axis?)() => void두 그리드를 함께 스크롤합니다('both', 'vertical', 'horizontal'). 연결을 끊는 함수를 돌려줍니다.
rowDragEnd / rowDropeventsrowDragEnd { row, fromIndex, toIndex }는 그리드 안에서 옮긴 뒤, rowDrop { row, target, position, fromOtherGrid, fromGrid }은 행을 받은 그리드에서 발생합니다.

이 페이지에 대한 질문

행을 끌어서 순서를 바꿀 수 있나요?

네. rowDrag: true를 주면 끌기 손잡이가 생기고 rowDragEnd가 새 위치를 알려 줍니다. group 이름을 주면 그리드끼리, 트리에서는 다른 부모로도 옮길 수 있습니다.