Spans, wrapping & layout
Cells spanning rows and columns, wrapped text with growing rows, header buttons, right-to-left and card view.
Code for this demo
import { createGrid, type ColumnDef, type GridApi, 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 });
const regions = [
{ region: h('Seoul', '서울'), items: [[h('Monitor', '모니터'), h('Pending payment for the March order; follow up with the buyer before the end of the week.', '3월 주문 입금 대기 중입니다. 이번 주가 끝나기 전에 구매 담당자에게 다시 연락해야 합니다.')], [h('Keyboard', '키보드'), h('Shipped.', '출고 완료.')], [h('Desk', '책상'), h('Assembly service booked for Friday afternoon, customer asked for a call 30 minutes before arrival.', '금요일 오후 조립 서비스 예약. 도착 30분 전에 전화해 달라는 요청이 있었습니다.')]] },
{ region: h('Busan', '부산'), items: [[h('Chair', '의자'), h('Back-ordered: the supplier expects new stock in two weeks.', '재고 없음: 공급처 입고까지 2주 예상.')], [h('Lamp', '조명'), h('Delivered.', '배송 완료.')]] },
{ region: h('Tokyo', '도쿄'), items: [[h('Monitor arm', '모니터 암'), h('Customs paperwork done.', '통관 서류 처리 완료.')], [h('Cable kit', '케이블 세트'), h('Gift wrap requested.', '선물 포장 요청.')]] },
];
const data: Row[] = [];
for (const r of regions) {
r.items.forEach(([item, memo], i) => data.push({
region: r.region, regionRows: i === 0 ? r.items.length : 1, item, memo,
qty: 1 + ((data.length * 7) % 9), price: 30 + ((data.length * 37) % 400),
}));
data.push({ kind: 'note', item: h(`${r.region}: shipping cut-off is 3 pm`, `${r.region}: 오후 3시 이후 주문은 다음 날 출고`) });
}
const columns: ColumnDef[] = [
// the first row of each region spans all of its rows
{ field: 'region', header: h('Region', '지역'), width: 110, rowSpan: ({ row }) => row.regionRows ?? 1 },
// note rows span the item, memo and qty columns
{ field: 'item', header: h('Item', '품목'), width: 150, colSpan: ({ row }) => (row.kind === 'note' ? 3 : 1),
cellClass: ({ row }) => (row.kind === 'note' ? 'demo-note' : undefined) },
// long memos wrap; autoRowHeight grows the row
{ field: 'memo', header: h('Memo', '메모'), width: 280, wrapText: true },
{ field: 'qty', header: h('Qty', '수량'), type: 'number', width: 90 },
{
field: 'price', header: h('Price', '단가'), type: 'number', format: '#,##0', width: 120, headerClass: 'demo-money',
// a help button in the header; pressing it does not sort
headerRenderer: ({ header }) => {
const span = document.createElement('span');
const b = document.createElement('button');
b.type = 'button';
b.textContent = '?';
b.className = 'demo-help';
b.addEventListener('click', () => ctx.log(h('Prices are in USD, before tax.', '단가는 달러, 세전 금액입니다.')));
span.append(`${header} `, b);
return span;
},
},
];
let grid: GridApi;
let rtl = false;
let cards = false;
const create = () => {
grid?.destroy();
grid = createGrid(ctx.host, {
columns, data, locale: lang, rangeSelection: true,
autoRowHeight: true,
direction: rtl ? 'rtl' : 'ltr',
// cardView: a width in px; below it rows turn into cards (phones). 99999 forces cards here.
cardView: cards ? 99999 : false,
});
};
create();
text(ctx, h('Region cells span their rows, note rows span three columns, long memos wrap and the row grows. Press ? in the Price header.',
'지역 셀은 여러 행을, 안내 행은 세 칸을 차지합니다. 긴 메모는 줄바꿈되고 행 높이가 늘어납니다. 단가 헤더의 ? 버튼도 눌러 보세요.'));
separator(ctx);
toggle(ctx, h('Right to left (RTL)', '오른쪽→왼쪽 (RTL)'), false, on => { rtl = on; create(); ctx.log(`direction: ${on ? 'rtl' : 'ltr'}`); });
toggle(ctx, h('Card view (phones)', '카드 보기 (휴대폰)'), false, on => { cards = on; create(); ctx.log(`cardView: ${on}`); });
return grid!;
}