페이지와 서버 데이터
pagination을 켜면 한 번에 한 쪽씩 보여 주고 아래에 쪽 이동 막대가 붙습니다. 처음·이전·다음·마지막 쪽으로 이동할 수 있고, pageSizes를 주면 쪽당 행 수도 고를 수 있습니다. 정렬하거나 필터를 바꾸면 첫 쪽으로 돌아갑니다.
실행 예제페이지와 서버 데이터
행을 쪽 단위로 넘기거나, 스크롤에 맞춰 서버에서 필요한 만큼만 불러옵니다.
이 데모의 코드
import { createGrid, type ColumnDef, type GridApi, type GridOptions, type Row, type ServerParams } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';
type Mode = 'pages' | 'server' | 'infinite' | 'serverPages';
/** A pretend server in the browser: sorts, filters and answers after a short delay. */
function fakeServer(rows: Row[], log: (m: string) => void, knowsTotal = true) {
return {
getRows(p: ServerParams): Promise<{ rows: Row[]; total?: number }> {
let list = rows;
const q = p.quickFilter.trim().toLowerCase();
if (q) list = list.filter(r => `${r.name} ${r.company} ${r.country}`.toLowerCase().includes(q));
const s = p.sort[0];
if (s) {
const dir = s.dir === 'desc' ? -1 : 1;
list = [...list].sort((a, b) => (a[s.colId] < b[s.colId] ? -dir : a[s.colId] > b[s.colId] ? dir : 0));
}
log(`getRows → ${p.start}–${p.end}${s ? ` sort ${s.colId} ${s.dir}` : ''}${q ? ` "${q}"` : ''}`);
const page = list.slice(p.start, p.end);
return new Promise(resolve => setTimeout(() => resolve(knowsTotal ? { rows: page, total: list.length } : { rows: page }), 350));
},
};
}
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
const h = (en: string, ko: string) => tr(lang, { en, ko });
const columns: ColumnDef[] = [
{ field: 'id', header: 'ID', type: 'number', width: 90 },
{ field: 'name', header: h('Name', '이름'), width: 170 },
{ field: 'company', header: h('Company', '회사'), width: 150 },
{ field: 'country', header: h('Country', '국가'), width: 150, filter: false },
{ field: 'total', header: h('Total', '합계'), type: 'number', format: '#,##0', width: 130 },
{ field: 'joined', header: h('Joined', '가입일'), type: 'date', width: 130 },
];
const big = makeRows(100000);
let grid: GridApi | null = null;
const show = (mode: Mode) => {
grid?.destroy();
const options: Partial<GridOptions> =
mode === 'pages' ? { data: makeRows(1000), pagination: { pageSize: 50, pageSizes: [20, 50, 100] } }
: mode === 'server' ? { datasource: fakeServer(big, ctx.log), blockSize: 100 }
: mode === 'infinite' ? { datasource: fakeServer(big, ctx.log, false), blockSize: 100 }
: { datasource: fakeServer(big, ctx.log), pagination: { pageSize: 50, pageSizes: [20, 50, 100] } };
// the pretend server only understands sorting and the quick search
const cols = mode === 'pages' ? columns : columns.map(c => ({ ...c, filter: false as const }));
grid = createGrid(ctx.host, { columns: cols, locale: lang, theme: 'erp', statusBar: true, ...options });
grid.on('pageChange', e => ctx.log(`pageChange → ${e.page + 1} / ${e.pageCount}`));
grid.on('rowsLoaded', e => ctx.log(`rowsLoaded → ${e.start}–${e.end}${e.total !== undefined ? ` of ${e.total}` : ''}`));
};
text(ctx, h('The server modes fetch 100 rows at a time from a pretend server with a short delay. Scroll fast to see the placeholders.',
'서버 모드는 지연이 있는 가짜 서버에서 100행씩 받아 옵니다. 빠르게 스크롤하면 불러오는 동안의 자리 표시를 볼 수 있습니다.'));
separator(ctx);
button(ctx, h('Pages (1,000 rows in the browser)', '페이지 (브라우저의 1,000행)'), () => show('pages'));
button(ctx, h('Server, 100,000 rows', '서버, 10만 행'), () => show('server'));
button(ctx, h('Infinite scroll', '무한 스크롤'), () => show('infinite'));
button(ctx, h('Server pages', '서버 페이지'), () => show('serverPages'));
separator(ctx);
button(ctx, h('Search "Kim" on the server', '서버에서 "Kim" 검색'), () => grid?.setQuickFilter('Kim'));
button(ctx, h('Clear search', '검색 지우기'), () => grid?.setQuickFilter(''));
button(ctx, h('Reload rows', '다시 불러오기'), () => grid?.refreshRows());
show('pages');
return grid;
}실행 예제서버 측 그룹핑
그룹과 건수·합계를 서버에서 받아, 펼칠 때마다 불러옵니다.
이 데모의 코드
import { createGrid, type Row, type ServerParams } from '@freegridjs/core';
import { makeRows } from '../data';
import { select, separator, text, 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 all = makeRows(20000);
/**
* A pretend server. With groupBy it answers, for the open groups (groupKeys),
* one row per group with its count and totals — what a GROUP BY query returns.
*/
const datasource = {
getRows(p: ServerParams) {
const by = p.groupBy ?? [];
const keys = p.groupKeys ?? [];
let rows = all.filter(r => keys.every((k, i) => r[by[i]] === k));
let answer: { rows: Row[]; total?: number };
if (keys.length < by.length) {
const col = by[keys.length];
const groups = new Map<unknown, { count: number; q1: number; total: number }>();
for (const r of rows) {
const g = groups.get(r[col]) ?? { count: 0, q1: 0, total: 0 };
g.count++;
g.q1 += r.q1;
g.total += r.total;
groups.set(r[col], g);
}
const desc = p.sort.find(s => s.colId === col)?.dir === 'desc';
answer = { rows: [...groups].sort((a, b) => String(a[0]).localeCompare(String(b[0])) * (desc ? -1 : 1)).map(([v, g]) => ({ [col]: v, ...g })) };
ctx.log(`getRows(groupKeys: ${JSON.stringify(keys)}) → ${answer.rows.length} ${h('groups', '그룹')}`);
} else {
rows = rows.slice(p.start, p.end);
answer = { rows, total: rows.length };
ctx.log(`getRows(groupKeys: ${JSON.stringify(keys)}) → ${rows.length} ${h('rows', '행')}`);
}
return new Promise<typeof answer>(resolve => setTimeout(() => resolve(answer), 250));
},
};
const grid = createGrid(ctx.host, {
columns: [
{ field: 'country', header: h('Country', '국가'), width: 160 },
{ field: 'company', header: h('Company', '회사'), width: 160 },
{ field: 'name', header: h('Name', '이름'), width: 160 },
{ field: 'q1', header: 'Q1', type: 'number', format: '#,##0', width: 130, aggFunc: 'sum' },
{ field: 'total', header: h('Total', '합계'), type: 'number', format: '#,##0', width: 140, aggFunc: 'sum' },
],
datasource, getRowId: r => String(r.id), locale: lang,
groupBy: ['country', 'company'],
});
text(ctx, h('20,000 rows stay on the "server". Opening a group asks only for what is inside it; the log shows each request.',
'2만 행은 "서버"에만 있습니다. 그룹을 펼칠 때 그 안에 든 것만 요청합니다. 아래 기록에서 요청을 볼 수 있습니다.'));
separator(ctx);
select(ctx, h('Group by', '그룹 기준'), [['country,company', h('Country → Company', '국가 → 회사')], ['company', h('Company', '회사')], ['', h('None', '없음')]], 'country,company',
v => grid.setGroupBy(v ? v.split(',') : []));
return grid;
}pagination을 켜면 한 번에 한 쪽씩 보여 주고 아래에 쪽 이동 막대가 붙습니다. 처음·이전·다음·마지막 쪽으로 이동할 수 있고, pageSizes를 주면 쪽당 행 수도 고를 수 있습니다. 정렬하거나 필터를 바꾸면 첫 쪽으로 돌아갑니다.
데이터가 서버에 있다면 data 대신 datasource를 넘기세요. 그리드는 화면에 보이는 행만 blockSize 단위로 getRows에 요청하고, 도착할 때까지 회색 자리 표시를 그립니다. 정렬, 필터, 빠른 검색 조건이 함께 전달되므로 서버에서 처리하면 됩니다. 전체 개수를 알면 total을 돌려주고, 모르면 빼 두세요. 그러면 무한 스크롤이 되어 짧은 블록이 올 때까지 계속 불러옵니다. 여기에 pagination까지 켜면 한 쪽이 요청 한 번이 됩니다. 그룹핑과 행 추가·삭제는 브라우저에 데이터가 있을 때만 쓸 수 있습니다.
// Pages in the browser
createGrid(el, { columns, data, pagination: { pageSize: 50, pageSizes: [20, 50, 100] } });
// Rows from your server, 100 at a time, as people scroll
createGrid(el, {
columns,
datasource: {
async getRows({ start, end, sort, filters, quickFilter }) {
const res = await fetch('/api/orders?' + new URLSearchParams({
start, end, sort: JSON.stringify(sort), filters: JSON.stringify(filters), q: quickFilter,
}));
const { rows, total } = await res.json();
return { rows, total }; // leave out total for infinite scroll
},
},
blockSize: 100,
// pagination: { pageSize: 50 }, // add this for server-side pages
});