그리드 간 행 드래그
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));| 이름 | 타입 | 설명 |
|---|---|---|
group | string | 같은 group의 그리드끼리 행을 주고받음. |
copy | boolean | '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를 줄 수 있습니다.