nexabase-report
v0.5.0
Published
Professional report designer and viewer for NexaBase — drag & drop designer, PDF/Excel export, charts, crosstabs, subreports.
Maintainers
Readme
nexabase-report
Professional report designer and viewer — framework-agnostic, client-side PDF/Excel/Word export, banded report model inspired by Stimulsoft & DevExpress.
Features
- WYSIWYG Designer — drag & drop bands, elements, charts, crosstabs, barcodes, QR codes
- Framework-agnostic Viewer — Custom Element (
<nexa-viewer>) works with Vue, React, Angular, Blazor, jQuery, or plain HTML - Client-side Export — PDF (selectable text), Excel, Word, CSV — no server required
- Banded Report Model — ReportHeader, PageHeader/Footer, DataBand, GroupHeader/Footer, ReportFooter
- Charts & Crosstabs — ECharts integration, dynamic pivot tables with aggregations
- Dashboard Designer — widgets (chart, gauge, indicator, table, filter, pivot, text, image)
- Expression Engine — safe evaluation without
eval—FormatNumber,IIF,ISNULL,UPPER, etc. - Master-Detail & Drill-down — nested data sources, subreports, clickable drill-through
- Conditional Formatting — dynamic styles based on data values
- Parameters — typed report parameters with validation dialog
- i18n — built-in Spanish, English, Portuguese
Installation
npm install nexabase-reportQuick Start
Plain HTML / Any Framework
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="node_modules/nexabase-report/dist/style.css">
</head>
<body>
<nexa-viewer id="viewer" minimal></nexa-viewer>
<script src="node_modules/nexabase-report/dist/nexabase-report.umd.js"></script>
<script>
const viewer = document.getElementById('viewer');
viewer.definition = { /* report JSON */ };
viewer.data = [ /* array of records */ ];
</script>
</body>
</html>Vue 3
<script setup>
import { registerNexaReport } from 'nexabase-report';
import 'nexabase-report/style.css';
registerNexaReport();
</script>
<template>
<nexa-viewer :definition="reportDef" :data="reportData" minimal />
</template>React
import { useEffect, useRef } from 'react';
import 'nexabase-report';
import 'nexabase-report/style.css';
function ReportViewer({ definition, data }: { definition: any; data: any[] }) {
const ref = useRef<any>(null);
useEffect(() => {
if (ref.current) {
ref.current.definition = definition;
ref.current.data = data;
}
}, [definition, data]);
return <nexa-viewer ref={ref} minimal style={{ width: '100%', height: '100%' }} />;
}Blazor
<nexa-viewer id="viewer" minimal></nexa-viewer>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initViewer", reportDef, data);
}
}
}// wwwroot/report.js
function initViewer(def, data) {
const viewer = document.getElementById('viewer');
viewer.definition = def;
viewer.data = data;
}Viewer API
Props
| Prop | Type | Description |
|------|------|-------------|
| definition | NexaReportDefinition \| string | Report definition (object or JSON string) |
| data | any[] \| Record<string, any[]> | Data — array for single datasource, object for multiple |
| minimal | boolean | Hide toolbar and sidebar |
| parameters | Record<string, any> | Pre-fill parameter values (skips dialog) |
| skipParamsDialog | boolean | Don't show parameter input dialog |
| currentPage | number | Externally controlled page number |
| locale | 'es' \| 'en' \| 'pt' | UI language |
| licenseKey | string | License key (optional) |
Methods
| Method | Returns | Description |
|--------|---------|-------------|
| exportPdf() | Promise<void> | Export to PDF and download |
| exportPdfAsBlob() | Promise<Blob> | Export to PDF as Blob (for ZIP, upload, etc.) |
| exportExcel() | Promise<void> | Export to Excel and download |
| exportWord() | Promise<void> | Export to Word and download |
| exportCsv() | Promise<void> | Export to CSV and download |
| printReport() | void | Print the report |
| updateData(data) | void | Update data without re-rendering definition |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| data-request | { alias: string } | Request data for a datasource |
| drill-click | { element, sourceData, targetReportId, parameters } | Drill-down element clicked |
| subreport-toggle | { subReportId, collapsed } | Subreport expanded/collapsed |
| page-change | { page: number } | Page changed |
Batch Export (ZIP)
Export multiple reports to a single ZIP file without showing the viewer:
import { registerNexaReport } from 'nexabase-report';
import JSZip from 'jszip';
registerNexaReport();
async function exportBatch(reportDef, invoices) {
// Create hidden viewer
const viewer = document.createElement('nexa-viewer');
viewer.style.display = 'none';
viewer.minimal = true;
viewer.definition = reportDef;
document.body.appendChild(viewer);
await customElements.whenDefined('nexa-viewer');
await new Promise(r => setTimeout(r, 300));
const zip = new JSZip();
for (const invoice of invoices) {
viewer.updateData(invoice.items);
await new Promise(r => setTimeout(r, 500));
const blob = await viewer.exportPdfAsBlob();
zip.file(invoice.name + '.pdf', blob);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
// Download
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'invoices.zip';
a.click();
URL.revokeObjectURL(url);
// Cleanup
document.body.removeChild(viewer);
}Designer
The designer is a Vue 3 component for building reports visually:
<script setup>
import NexaDesigner from 'nexabase-report/src/lib/designer/NexaDesigner.vue';
import 'nexabase-report/style.css';
</script>
<template>
<NexaDesigner :report="reportDefinition" @save="handleSave" />
</template>Report Model
Reports use a banded structure:
{
"metadata": { "name": "Sales Report", "version": "1.0" },
"layout": {
"page": { "format": "A4", "orientation": "portrait", "margins": { "top": 2, "right": 2, "bottom": 2, "left": 2 } },
"bands": [
{ "id": "header", "type": "ReportHeader", "height": 60, "elements": [...] },
{ "id": "data", "type": "DataBand", "height": 30, "dataSource": "main", "elements": [...] },
{ "id": "footer", "type": "PageFooter", "height": 30, "elements": [...] }
]
},
"dataSources": [{ "id": "ds1", "alias": "main", "collection": "sales" }]
}Band Types
| Type | Description |
|------|-------------|
| ReportHeader | Renders once, on the first page |
| PageHeader | Renders at the top of every page |
| DataBand | Iterates over datasource records |
| GroupHeader / GroupFooter | Renders when group key changes |
| PageFooter | Renders at the bottom of every page |
| ReportFooter | Renders once, on the last page |
Element Types
Text, Image, Barcode, QRCode, Rectangle, Ellipse, Line, Arrow, Table, Chart, Crosstab, SubReport, DrillDown
Expression Engine
Safe evaluation without eval. Supports:
- Bindings:
{{field}}— resolves from data row - Expressions:
{[expr]}— evaluated with context - System variables:
{[Page]},{[TotalPages]},{[Today]},{[RowNumber]},{[TotalRows]},{[EvenRow]},{[OddRow]} - Functions:
FormatNumber,FormatDate,FormatCurrency,IIF,ISNULL,UPPER,LOWER,SUBSTRING,ABS,CEIL,FLOOR,ROUND,LEN,TRIM,CONCAT
Integration with NexaBase
Works out of the box with NexaBase backend:
import { nexaService } from 'nexabase-report';
// Connect
nexaService.connect('https://your-nexabase.url', 'your-api-key');
// Fetch data and render
const data = await nexaService.listDocuments('sales');
viewer.data = data;Examples
See examples/ directory in the repository for working samples:
integration-vue.html— Vue 3 + UMD buildintegration-react.html— React + UMD buildintegration-plain.html— Plain HTML + UMD build
Development
pnpm install
pnpm run dev # Start dev server
pnpm run build # Build library
pnpm run test # Run tests (199 tests)
pnpm run test:watch # Watch modePublishing
pnpm run release:minor # Bump minor version
pnpm publish --access publicLicense
MIT © NexaBase Team
