FreeGridv1.15.1English

그리드 간 행 드래그

rowDrag group이 같은 그리드끼리 행을 주고받습니다. 놓으면 이동하고, rowDrag.copy(또는 놓을 때 Ctrl)는 복사하며, copyRow로 복사본에 새 id를 주고, canDrop으로 대상을 거부합니다.

실행 예제그리드 간 행 드래그

상품을 주문으로 복사하고 주문 줄 순서 바꾸기.

이 데모의 코드
import { createGrid, type GridApi, type Row } from '@freegridjs/core';
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 });
  ctx.host.style.display = 'grid';
  ctx.host.style.gridTemplateColumns = 'repeat(auto-fit, minmax(300px, 1fr))';
  ctx.host.style.gap = '16px';
  ctx.host.style.gridAutoRows = '100%';
  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.append(left, right);

  const names = h('Keyboard,Mouse,Monitor,Laptop stand,USB-C hub,Webcam,Headset,Desk lamp,Chair,Notebook', '키보드,마우스,모니터,노트북 거치대,USB-C 허브,웹캠,헤드셋,스탠드,의자,노트').split(',');
  const catalogue: Row[] = names.map((name, i) => ({ id: `p${i + 1}`, name, price: 19000 + i * 7000 }));
  let next = 1;
  const products = createGrid(left, {
    columns: [{ field: 'name', header: h('Products (copied out)', '상품 (복사됨)'), flex: 1 }, { field: 'price', header: h('Price', '가격'), type: 'number', format: '#,##0', width: 100 }],
    data: catalogue, locale: ctx.lang, getRowId: r => String(r.id),
    rowDrag: {
      group: 'order',
      copy: true,
      // the order line gets its own id and a quantity
      copyRow: p => ({ id: `line${next++}`, productId: p.id, name: p.name, price: p.price, qty: 1 }),
    },
  });
  // canDrop reads this grid, so its type is written out
  const order: GridApi = createGrid(right, {
    columns: [
      { field: 'name', header: h('Order', '주문'), flex: 1 },
      { field: 'qty', header: h('Qty', '수량'), type: 'number', width: 70, editable: true },
      { id: 'amount', header: h('Amount', '금액'), type: 'number', format: '#,##0', width: 100, valueGetter: r => r.price * r.qty },
    ],
    data: [], locale: ctx.lang, getRowId: r => String(r.id),
    rowDrag: {
      group: 'order',
      // a product goes in once; moving lines inside the order is fine
      canDrop: p => !p.fromOtherGrid || !order.getData().some(r => r.productId === p.row.id),
    },
    noRowsOverlay: h('Drag products here', '상품을 여기로 끌어 오세요'),
  });
  order.on('rowDrop', e => ctx.log(e.fromOtherGrid ? `${e.copy ? h('copied', '복사') : h('moved', '이동')}: ${e.row.name}` : `${h('reordered', '순서 변경')}: ${e.row.name}`));
  text(ctx, h('Drag a product by its handle into the order: it is copied, and the same product is refused a second time. Drag order lines to reorder them.',
    '손잡이로 상품을 주문에 끌어 놓으면 복사되고, 같은 상품은 두 번 들어가지 않습니다. 주문 줄은 끌어서 순서를 바꿉니다.'));
  separator(ctx);
  button(ctx, h('Order total', '주문 합계'), () => ctx.log(`${order.getData().reduce((s, r) => s + r.price * r.qty, 0).toLocaleString()}`));
  button(ctx, h('Undo in order', '주문에서 실행 취소'), () => order.undo());
  return products;
}

나란히 놓인 두 목록(상품 목록과 주문, 백로그와 이번 주 할 일)에서는 한쪽 행을 다른 쪽으로 옮기거나 복사할 일이 많습니다. 두 그리드에 같은 group으로 rowDrag를 주면, 손잡이로 끈 행을 다른 그리드의 행 사이에 놓을 수 있습니다.

const backlog = createGrid(left, { columns, data: tasks, getRowId: r => String(r.id), rowDrag: { group: 'tasks' } });
const thisWeek = createGrid(right, { columns, data: [], getRowId: r => String(r.id), rowDrag: { group: 'tasks' } });

thisWeek.on('rowDrop', e => {
  if (e.fromOtherGrid) save({ id: e.row.id, list: 'this-week' });
});

이동 대신 복사

행이 나오는 그리드에 copy: true를 주면, 다른 그리드에 놓을 때 그쪽에 복사본이 들어가고 원래 행은 그대로 남습니다. 상품을 주문에 넣어도 상품 목록은 그대로입니다. 이 옵션이 없어도 놓을 때 Ctrl(맥은 ⌘)을 누르면 복사됩니다. copy: 'ctrl'이면 그 방법으로만 복사하고, copy: false면 복사하지 않습니다. 놓을 자리 선에 +가 보이면 복사된다는 뜻입니다.

const products = createGrid(left, {
  columns, data: catalogue, getRowId: r => String(r.id),
  rowDrag: {
    group: 'order',
    copy: true,
    // the copy gets its own id and a quantity; nested objects are copied too
    copyRow: p => ({ id: crypto.randomUUID(), productId: p.id, name: p.name, price: p.price, qty: 1 }),
  },
});
// canDrop reads the grid itself, so give it a type (TypeScript cannot infer it)
const order: GridApi = createGrid(right, {
  columns: orderColumns, data: [], getRowId: r => String(r.id),
  rowDrag: {
    group: 'order',
    // only products come in, and never twice
    canDrop: p => p.fromOtherGrid && !order.getData().some(r => r.productId === p.row.id),
  },
});
order.on('rowDrop', e => console.log(e.copy ? 'added' : 'moved', e.row));
이름타입설명
groupstring같은 group의 그리드끼리 행을 주고받음.
copyboolean | 'ctrl'행이 나오는 그리드에 줌. true: 다른 그리드에 놓으면 복사. 'ctrl': Ctrl / ⌘를 누를 때만. false: 복사 안 함. 기본: Ctrl / ⌘면 복사, 그냥 놓으면 이동.
copyRow(row) => Row다른 그리드로 들어갈 복사본. 기본은 행의 값(중첩 객체도 복사). 두 그리드의 id가 겹치면 여기서 새 id를 주세요. getRowId가 있으면 이미 있는 id의 행은 놓이지 않습니다(기본 복사본은 id가 같아 한 번만 복사됨).
canDrop(params) => boolean받는 그리드에 줌. params: row, target, position, fromOtherGrid, copy. false면 그 자리를 거부.

이벤트와 실행 취소

  • 받는 그리드에서 rowDrop이 fromOtherGrid: true, fromGrid(상대 그리드), 복사였다면 copy와 함께 발생합니다. 옮겨진 행은 떠난 그리드에서 rowsRemoved, 새 그리드에서 rowsAdded도 발생합니다.
  • 각 그리드가 자기 몫을 기록합니다. 받는 그리드에서 Ctrl+Z를 누르면 행이 다시 빠지고, 떠난 그리드에서 Ctrl+Z를 누르면 돌아옵니다.
  • 추가·삭제된 행은 변경(getModifiedRows)으로 잡혀 저장 버튼에서 보입니다.

놓을 수 없는 경우

  • 정렬·필터·그룹 중인 그리드는 화면 위치가 데이터 위치가 아니어서 행을 받지 않습니다. copy가 있는 그리드는 정렬·필터 중에도 끌어 내보낼 수 있습니다.
  • 받는 그리드에 이미 있는 행(같은 객체)은 두 번 들어가지 않습니다.
  • 서버 데이터(datasource)는 참여하지 않습니다. 서버를 바꾸고 트랜잭션을 보내세요.

한 그리드 안의 순서 바꾸기와 트리 데이터는 행 드래그에 있습니다.

이 페이지에 대한 질문

한 그리드의 행을 다른 그리드로 끌어 옮기려면?

두 그리드 모두 같은 group으로 rowDrag를 켜세요: rowDrag: { group: 'tasks' }. 손잡이로 행을 끌어 다른 그리드에 놓으면 그쪽으로 옮겨지고, 받는 그리드에서 rowDrop이 발생합니다.

첫 그리드에 행을 남기고 복사할 수 있나요?

네. 행이 나오는 그리드에 rowDrag.copy: true를 주거나, 놓을 때 Ctrl(맥은 ⌘)을 누르세요. copyRow로 복사본에 새 id를 줄 수 있습니다.