Events
Viewer and designer events with examples: onReady, onParamsSubmit, onPageChange, onError, onOutput, fetchData, onChange and onSave.
Events let your page react to what the viewer and the designer do. They are plain functions in the options, and none are required. In the order they happen when a user runs a report:
| Event | Where | When |
|---|---|---|
onParamsSubmit(params) | viewer | The user pressed Run. Return false to stay, or changed parameters. Not called for setParams(). |
fetchData(params) | viewer | Load rows: at start, on Run and on setParams(). Return { rows } (or a promise). |
onReady({ pages, rows, ms, params }) | viewer | The report is laid out and shown (after every run). |
onPageChange(page, total) | viewer | The page in view changed. |
onOutput(e) | viewer · createReport | Before every print and file (print, pdf, xlsx, xlsx-data, csv, hwpx). Return false to cancel. |
onError(error, during) | viewer | Something failed: 'data', 'layout' or 'output'. |
onChange(definition) | designer | Every change to the form, including undo and redo. |
onSave(definition) | designer | The user pressed Save or Ctrl+S. |
Viewer events
Validate the parameters before loading, show counts after each run, follow the page, handle errors in one place and log every output:
const viewer = createViewer(el, {
definition,
params: { from: '2026-10-01', to: '2026-10-31', dept: '' },
// 1. the user pressed 조회/Run: check or adjust the parameters first
onParamsSubmit: params => {
if (String(params.from) > String(params.to)) {
alert('시작일이 종료일보다 늦습니다.');
return false; // stay on the panel, nothing is loaded
}
return { ...params, dept: params.dept || myDept }; // or return changed parameters
},
// 2. load the rows for those parameters (start, 조회, setParams)
fetchData: params => fetch('/api/sales?' + new URLSearchParams(params as Record<string, string>)).then(r => r.json()),
// 3. after every successful run
onReady: ({ pages, rows, ms, params }) => {
summary.textContent = pages + '쪽 · ' + rows.toLocaleString() + '건 (' + ms + 'ms)';
printBtn.disabled = rows === 0;
},
// 4. the page in view changed (scroll, buttons, keyboard, goTo)
onPageChange: (page, total) => { pager.textContent = page + ' / ' + total; },
// 5. loading, layout, printing or saving failed (the viewer also shows a message)
onError: (error, during) => {
if (during === 'data' && /401/.test(error.message)) location.href = '/login';
else reportError(error); // your error tracker
},
// 6. before every print and file: log it, ask for a reason, or return false to block it
onOutput: async e => {
const reason = e.kind === 'print' ? prompt('출력 사유를 입력하세요') : '';
if (reason === null) return false;
await fetch('/api/report-logs', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...e, reason }) });
},
});- onParamsSubmit runs only when the user presses Run; viewer.setParams() from your code goes straight to fetchData.
- onReady runs after the first load and after every Run, setData, setParams and setDefinition, so it is the place to update counts and buttons around the viewer.
- If an event handler throws, the error goes to the console and the viewer keeps working.
Designer events
Keep a draft while the user edits, save to your server, and warn before leaving with unsaved changes:
let dirty = false;
const designer = createDesigner(el, {
definition, data: { rows: sample },
// every change (move, resize, property, undo/redo): keep a draft, mark the page as changed
onChange: def => {
dirty = true;
localStorage.setItem('draft:sales', JSON.stringify(def));
},
// Ctrl+S or the Save button
onSave: async def => {
const res = await fetch('/api/reports/sales', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(def) });
if (res.ok) { dirty = false; localStorage.removeItem('draft:sales'); }
},
});
addEventListener('beforeunload', e => { if (dirty) e.preventDefault(); }); // unsaved changes warningIn React, Vue, Svelte and Angular
// React
<FreeReportViewer definition={def} fetchData={load} onReady={e => setCount(e.rows)} onPageChange={setPage} onError={showError} />
<!-- Vue -->
<FreeReportViewer :definition="def" :fetch-data="load" :on-ready="e => (count = e.rows)" :on-page-change="p => (page = p)" />
<!-- Svelte 5: onReady gives the viewer itself; the core onReady is onRendered -->
<FreeReportViewer {definition} fetchData={load} onRendered={e => (count = e.rows)} onPageChange={p => (page = p)} />
<!-- Angular: outputs -->
<free-report-viewer [definition]="def" [fetchData]="load" (rendered)="count = $event.rows" (pageChange)="page = $event.page" (error)="show($event.error)" />The output event (onOutput) has its own page with an output log example. Output log hook