v1.6.0

<bmx-report-viewer>

A report, laid out in pages and shown the way it will print: scroll through it, zoom, search it, jump from its contents, fill in its parameters, print it, or download it as a PDF - all in the browser, with no report server.

15 properties · 5 events · 12 methods · 29 parts

Example

Everything runs in the browser. The report is a JSON definition and the rows are an array; the viewer lays them out into A4 pages, and the same pages print and download as a tagged PDF. Open Parameters to pick a region, type in Find, or open the side panel for the contents. Close a region with the arrow beside it, click a column heading to sort, and use Export for an Excel workbook, a CSV file or a web page.
Show markup
<bmx-report-viewer id="ex-rv" style="--bmx-report-viewer-height: 34rem"></bmx-report-viewer>

<div class="row" style="margin-block-start: 1rem">
  <span class="note" id="ex-rv-out">
    <strong>Everything runs in the browser.</strong> The report is a JSON definition and the rows are an array; the
    viewer lays them out into A4 pages, and the same pages print and download as a tagged PDF. Open
    <strong>Parameters</strong> to pick a region, type in <strong>Find</strong>, or open the side panel for the
    contents. Close a region with the arrow beside it, click a column heading to sort, and use
    <strong>Export</strong> for an Excel workbook, a CSV file or a web page.
  </span>
</div>

<script type="module">
  await customElements.whenDefined('bmx-report-viewer');

  const viewer = document.getElementById('ex-rv');
  const out = document.getElementById('ex-rv-out');

  // The report: bands of positioned items, in points. Every computed value is an
  // expression in the report language - never script - so a definition from a
  // database is safe to open.
  viewer.report = {
    title: 'Orders by region',
    language: 'en-GB',
    currency: 'GBP',
    page: { size: 'A4', margin: 40 },
    parameters: [{ name: 'region', label: 'Region', type: 'choice', choices: [{ value: '', label: 'All regions' }, 'North', 'South'], default: '' }],
    filter: "params.region = '' or region = params.region",
    fields: [{ name: 'amount', value: 'quantity * price' }],
    reportHeader: {
      height: 210,
      items: [
        { type: 'text', x: 0, y: 0, width: 515, height: 24, text: '{{ ReportTitle }}', heading: 1, style: { size: 18, bold: true, color: '#1b3f8f' } },
        { type: 'text', x: 0, y: 26, width: 515, height: 12, text: '{{ count() }} orders worth {{ format(sum(amount), "C") }}', style: { color: '#52606d' } },
        // A chart is an item like any other: here, the whole report's sales by customer.
        { type: 'chart', x: 0, y: 46, width: 515, height: 150, kind: 'bar', category: 'customer', order: 'value', series: [{ value: 'amount', label: 'Sales' }], format: 'C0', labels: true },
      ],
    },
    // Headings with `sort` re-sort the report when the reader clicks them.
    pageHeader: {
      height: 18,
      items: [
        { type: 'text', x: 90, y: 3, width: 200, height: 11, text: 'CUSTOMER', sort: 'customer', style: { size: 7.5, bold: true, color: '#52606d' } },
        { type: 'text', x: 395, y: 3, width: 116, height: 11, text: 'AMOUNT', sort: 'amount', style: { size: 7.5, bold: true, color: '#52606d', align: 'end' } },
      ],
    },
    groups: [
      {
        by: 'region',
        bookmark: "region + ' region'",
        // The reader can close and open each region in the viewer.
        drilldown: 'expanded',
        header: { height: 24, items: [{ type: 'text', x: 16, y: 6, width: 300, height: 14, text: '{{ region }}', heading: 2, style: { size: 11, bold: true } }] },
        footer: {
          height: 22,
          items: [
            { type: 'line', x: 395, y: 2, width: 120, height: 0, color: '#9aa5b1' },
            { type: 'text', x: 395, y: 6, width: 120, height: 12, value: 'sum(amount)', style: { align: 'end', format: 'C', bold: true } },
          ],
        },
      },
    ],
    detail: {
      height: 15,
      alternateBackground: '#f5f7fa',
      items: [
        { type: 'text', x: 4, y: 2, width: 80, height: 11, value: 'date', style: { format: 'dd MMM yyyy' } },
        { type: 'text', x: 90, y: 2, width: 200, height: 11, value: 'customer' },
        { type: 'text', x: 300, y: 2, width: 60, height: 11, value: 'quantity', style: { align: 'end' } },
        { type: 'text', x: 395, y: 2, width: 116, height: 11, value: 'amount', style: { align: 'end', format: 'C' } },
      ],
    },
    pageFooter: {
      height: 14,
      items: [{ type: 'text', x: 315, y: 2, width: 200, height: 10, text: 'Page {{ PageNumber }} of {{ PageCount }}', style: { size: 7, align: 'end', color: '#7b8794' } }],
    },
  };

  const customers = ['Acme Ltd', 'Bolt & Co', 'Cogworks', 'Delta Supplies', 'Evergreen Farms', 'Fulcrum Tools'];
  viewer.data = Array.from({ length: 120 }, (_, i) => ({
    date: `2026-${String(1 + (i % 9)).padStart(2, '0')}-${String(1 + ((i * 7) % 28)).padStart(2, '0')}`,
    region: i % 3 ? 'North' : 'South',
    customer: customers[i % customers.length],
    quantity: 1 + ((i * 13) % 20),
    price: 20 + ((i * 37) % 180),
  }));

  viewer.addEventListener('bmxReportRender', event => {
    const { pageCount, rowCount } = event.detail;
    out.textContent = `bmxReportRender: ${rowCount} rows on ${pageCount} pages.`;
  });
</script>
<bmx-report-viewer
  report-url="/reports/sales-by-region.json"
  data-url="/api/orders"
></bmx-report-viewer>

The report is a JSON definition - page size, bands, groups, totals, formats - and the data is an array of rows. Both can be given as URLs, as attributes holding JSON, or as properties from script:

viewer.report = salesReport;
viewer.data = orders;
viewer.parameters = { region: 'North' };

WHAT THE READER GETS

Pages at their real size, laid out once and drawn the same on screen, on paper and in the PDF. A toolbar with page navigation, zoom (page width, whole page, or a percentage), find with every match highlighted, print, and PDF download. A side panel with page thumbnails and the report's contents, built from its group bookmarks. A parameters panel when the report asks for values, which runs the report again when the reader applies them.

ACCESSIBLE ON SCREEN AND IN THE PDF

The pages are real text in reading order, with headings and links a screen reader can navigate; the toolbar is buttons with names; the page and the search results are announced. The PDF it writes is tagged the same way.

INTERACTIVE REPORTS

A drill-down group shows a toggle that opens and closes it. A heading with a sort expression sorts the report when it is clicked. An item with a drill opens another report - one of reports, or fetched from a URL - with parameters worked out from its row, and Back returns to where the reader was.

EXPORTS

PDF, and the report's data as Excel or CSV, and the pages as a web page.

LARGE REPORTS

Layout runs in slices, so a hundred thousand rows never freeze the page, and only the pages in view are in the document at any moment.

Properties

PropertyAttributeTypeDefaultDescription
data data readonly Record<string, unknown>[] | string [] The rows: an array of objects, or JSON text.
dataUrl data-url string — A URL to fetch the rows from, as a JSON array. Takes the place of data.
drillData property only (report: string, parameters: Readonly<Record<string, unknown>>, signal: AbortSignal) => readonly unknown[] | undefined | null | Promise<readonly unknown[] | undefined | null> — Rows for a drill-through, when it needs other rows than the viewer's: called with the report's name and the parameters it opens with. Return the rows, or nothing to use the viewer's own. Script only.
exports exports readonly BmxReportFormat[] | string ['pdf', 'xlsx', 'csv', 'html'] The exports the toolbar offers, in order: any of pdf, xlsx, csv, html. A space-separated list in markup.
fileName file-name string — The name the PDF is saved under. Defaults to the report's title.
label label string — The viewer's accessible name. Defaults to the report's title.
locale locale string — The locale for numbers and dates. Defaults to the report's language, then the page's.
parameters parameters Record<string, unknown> | string {} Parameter values: an object, or JSON text. The reader can change them in the parameters panel.
report report BmxReport | string | null null The report definition: an object, or JSON text.
reportUrl report-url string — A URL to fetch the report definition from. Used when report is not set.
reports property only Readonly<Record<string, BmxReport | string | BmxReportSource>> — Reports a drill-through can open, by name: definitions, or URLs to fetch them from, or { report, data } for a report with rows of its own. A name that is not here is fetched as a URL. Script only.
requestInit property only RequestInit — Options for both fetches - headers, credentials. Script only.
sidebar sidebar 'pages' | 'contents' | 'none' 'none' The side panel: pages (thumbnails), contents, or none.
toolbar toolbar boolean true Shows the toolbar.
zoom zoom string | number 'page-width' page-width, whole-page, or a scale: 1 is actual size, 1.5 is 150%.

Events

EventDetailDescription
bmxPageChange BmxReportPageDetail The page in view changed.
bmxParametersChange BmxReportParametersDetail The reader applied new parameter values.
bmxReportDrill BmxReportDrillDetail The reader opened a drill-through. Cancel it to open the report yourself.
bmxReportRender BmxReportRenderDetail The report has been laid out, or could not run for want of parameters.
bmxReportSort BmxReportSortDetail The reader sorted the report by clicking a column heading.

Methods

MethodSignatureDescription
back back() => Promise<boolean> Goes back from a drill-through to the report it was opened from. Resolves to whether there was one.
download download(format?: BmxReportFormat, fileName?: string) => Promise<void> Saves the report as a file, under fileName or the report's title.
downloadPdf downloadPdf(fileName?: string) => Promise<void> Saves the report as a PDF, under fileName or the report's title.
exportAs exportAs(format?: BmxReportFormat) => Promise<Blob> The report as a file: pdf, xlsx (its data), csv (its data) or html (its pages).
exportPdf exportPdf() => Promise<Blob> The report as a PDF file.
find find(text: string) => Promise<number> Finds text in the report and shows the first match. Resolves to the number of matches.
getDocument getDocument() => Promise<BmxReportDocument | null> The laid-out report: its pages, bookmarks and problems.
goToPage goToPage(page: number) => Promise<void> Scrolls to a page, 1-based.
print print() => Promise<void> Prints the report: every page, at its own paper size.
refresh refresh() => Promise<void> Lays the report out again - after the data behind a URL has changed, say.
zoomIn zoomIn() => Promise<void> Zooms in a step.
zoomOut zoomOut() => Promise<void> Zooms out a step.

Slots

SlotDescription
empty what to show when there is no report.
toolbar-end extra controls at the end of the toolbar.

CSS shadow parts

PartDescription
back-button
body
bookmark
button
contents
drill
empty
error
export-list
export-option
find
find-count
find-input
page
page-input
parameter
parameters
parameters-hint
run-button
sidebar
sidebar-tab
sort
status
thumbnail
thumbnails
toggle
toolbar
viewport
zoom

CSS custom properties

PropertyDescription
--bmx-report-viewer-canvas The area behind the pages.
--bmx-report-viewer-control The colour of a group's toggle and a heading's sort arrow on the page.
--bmx-report-viewer-control-hover The wash over a sortable heading or a drill-through under the pointer. @part toolbar - the row of controls. @part button - a toolbar button. The Back button is also back-button. @part export-list - the list of file types under the Export button. @part export-option - one of them. @part page-input - the page number field. @part zoom - the zoom list. @part find - the search group. @part find-input - the search field. @part find-count - "3/12" beside it. @part parameters - the parameters panel. @part parameters-hint - the note that required values are missing. @part parameter - one parameter's field. @part run-button - the button that runs the report. @part body - the side panel and the pages. @part sidebar - the side panel. @part sidebar-tab - its Pages and Contents buttons. @part thumbnails - the list of page thumbnails. @part thumbnail - one of them. @part contents - the contents list. @part bookmark - one entry in it. @part viewport - the scrolling area that holds the pages. @part page - one page. Pages are always white: they are paper. @part match - a search match. Also match-current. @part toggle - the button beside a group's heading that shows or hides its details. @part sort - a heading that sorts the report when clicked. @part drill - a value that opens another report when clicked. @part status - the "Preparing report" notice. @part empty - what is shown when there is no report. @part error - the message when the report could not be loaded.
--bmx-report-viewer-height How tall the viewer is.
--bmx-report-viewer-match A search match on the page.
--bmx-report-viewer-match-current The match being shown.
--bmx-report-viewer-page-shadow The shadow under each page.
--bmx-report-viewer-sidebar-width How wide the side panel is.