Live data
Rows updated by id from a feed, with changed cells flashing.
Code for this demo
import { createGrid, type Row } from '@freegridjs/core';
import { button, 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 symbols = ['SAMSUNG', 'SK HYNIX', 'NAVER', 'KAKAO', 'HYUNDAI', 'LG CHEM', 'POSCO', 'KB FIN', 'CELLTRION', 'KIA', 'SHINHAN', 'COUPANG'];
const data: Row[] = symbols.map((name, i) => {
const price = 20000 + i * 13700;
return { id: name, name, price, change: 0, volume: 1000 + i * 120, history: [price, price, price, price, price, price, price, price] };
});
const grid = createGrid(ctx.host, {
columns: [
{ field: 'name', header: h('Symbol', '종목'), width: 140, pinned: 'left' },
{ field: 'price', header: h('Price', '현재가'), type: 'number', format: '#,##0', width: 120 },
{ field: 'change', header: h('Change', '등락률'), type: 'number', format: '0.00%', width: 110,
cellClass: ({ value }) => (Number(value) > 0 ? 'demo-up' : Number(value) < 0 ? 'demo-down' : undefined) },
{ field: 'history', header: h('Last moves', '최근 흐름'), width: 140, sparkline: { type: 'area' }, sortable: false, filter: false },
{ field: 'volume', header: h('Volume', '거래량'), type: 'number', format: '#,##0', width: 120 },
],
data, locale: lang, getRowId: r => String(r.id), rowNumbers: true,
});
let timer = 0;
const tick = () => {
// a pretend market feed: a few symbols move each tick
const updates = data.filter(() => Math.random() < 0.35).map(r => {
const price = Math.max(1000, Math.round(r.price * (1 + (Math.random() - 0.5) * 0.02)));
const base = r.history[0] as number;
return { id: r.id, price, change: (price - base) / base, volume: (r.volume as number) + Math.round(Math.random() * 50), history: [...(r.history as number[]).slice(1), price] };
});
grid.applyTransactionAsync({ update: updates });
};
grid.on('transactionApplied', e => ctx.log(`transactionApplied → ${h('updated', '갱신')} ${e.updated.length}`));
text(ctx, h('Prices arrive like a websocket feed. Only the changed rows are updated, and changed cells flash. Sorting by Change keeps up as prices move.',
'웹소켓처럼 시세가 들어옵니다. 바뀐 행만 갱신되고 바뀐 셀이 깜빡입니다. 등락률로 정렬해 두면 순위가 계속 바뀝니다.'));
separator(ctx);
const start = () => { timer = window.setInterval(tick, 700); };
toggle(ctx, h('Live feed', '실시간 수신'), true, on => {
clearInterval(timer);
if (on) start();
});
button(ctx, h('Add a symbol', '종목 추가'), () => {
const name = `NEW ${Math.floor(Math.random() * 900 + 100)}`;
const row = { id: name, name, price: 10000, change: 0, volume: 0, history: Array(8).fill(10000) };
data.push(row);
grid.applyTransaction({ add: [row], addIndex: 0 });
});
button(ctx, h('Remove the last', '마지막 종목 삭제'), () => {
const row = data.pop();
if (row) grid.applyTransaction({ remove: [row] });
});
start();
// stop when the page goes away
window.addEventListener('pagehide', () => clearInterval(timer), { once: true });
return grid;
}