Getting started with the web component¶
pivotkit-element is the report builder as one web component, <pivot-report>, for pages without
React: Angular apps, PHP and other server-rendered pages, plain HTML. It brings its own React 18.3.1,
pivotkit-react, pivotkit-core and @dnd-kit, and renders into an open shadow root. Everything the
React component does works the same.
Install¶
With a bundler:
npm install pivotkit-element
import "pivotkit-element"; // defines <pivot-report>
Without a bundler, serve the package's whole dist/ directory and load its entry as a module. The
files in dist/chunks/ (ECharts, the export) are loaded by relative URL from the entry when they are
first needed, so index.js alone is not enough.
To get dist/ without a build: npm install pivotkit-element and copy
node_modules/pivotkit-element/dist/ to your web root (the examples below use
/assets/pivotkit-element/dist/), or npm pack pivotkit-element and take package/dist/ from the
tarball. A CDN that serves npm packages works the same way, as long as it serves the whole dist/
directory of one exact version.
The first report¶
<script type="module" src="/assets/pivotkit-element/dist/index.js"></script>
<pivot-report
locale="de-DE"
height="640"
default-view='{"rows":["Region"],"values":[{"field":"Sales","agg":"sum"}]}'
></pivot-report>
<script type="module" src="/assets/report.js"></script>
// report.js
const report = document.querySelector("pivot-report");
report.data = rows; // an array of objects; a property, never an attribute
report.templates = [{ name: "By region", view: { rows: ["Region"] } }];
report.addEventListener("pivot-view-change", (e) => {
localStorage.setItem("view", JSON.stringify(e.detail.view));
});
- Simple settings are attributes (
locale,theme,height,file-name, ...); the start view fits indefault-viewas JSON. - Data, templates, labels and field defaults are properties set from a script.
- Every change is a
pivot-view-changeevent; a click on a value ispivot-cell-click. All events bubble and cross the shadow root, so a listener on the document gets them too.
The example page shows all of it; its source is in
examples/element/.
Save views on a server¶
With save-templates the Views menu offers "Save current view" under This view (and Save for a view loaded from the user's own; a save may name a view that exists, which the page replaces). The element asks the page with
a pivot-template-save event (detail: name, view, scope: "user") and waits for the answer you pass to respondWith():
report.saveTemplates = true;
report.addEventListener("pivot-template-save", (e) => {
e.detail.respondWith(
fetch("/api/templates", { method: "POST", body: JSON.stringify({ name: e.detail.name, view: e.detail.view }) })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(r.statusText))))
.then((list) => (report.templates = list))
);
});
Call respondWith() in the listener itself, not after an await. The report shows "Saved" when the
promise resolves and the error's message when it rejects. Shared views, publishing with
can-publish (pivot-template-publish) and keeping the user's views in the browser instead
(template-store="browser") are in Templates and shared views.
PHP and other server-rendered pages¶
Write the rows as JSON into a <script type="application/json">, which the browser does not run, and
hand them to the element from a module script:
<script type="application/json" id="report-rows">
<?= json_encode($rows, JSON_HEX_TAG | JSON_HEX_AMP | JSON_THROW_ON_ERROR) ?>
</script>
<pivot-report id="report" locale="de-DE" file-name="tickets"></pivot-report>
<script type="module" src="/assets/pivotkit-element/dist/index.js"></script>
<script type="module" src="/assets/report.js"></script>
// report.js
document.getElementById("report").data = JSON.parse(document.getElementById("report-rows").textContent);
JSON_HEX_TAG writes < and > as \u003C and \u003E, so a value containing </script> cannot end
the block. A start view fits in the attribute:
default-view='<?= htmlspecialchars(json_encode($view), ENT_QUOTES) ?>' (ENT_QUOTES also encodes
', which PHP before 8.1 leaves alone by default and the attribute's quotes need). Data never goes
into an attribute.
Angular¶
The element is a standard custom element, so Angular's property and event bindings apply. This example is not built or tested in the pivotkit repository:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "pivotkit-element";
import type { PivotView } from "pivotkit-element";
@Component({
selector: "app-ticket-report",
standalone: true,
// <pivot-report> is no Angular component: the schema lets the template use it
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<pivot-report
[data]="rows"
[defaultView]="start"
[templates]="templates"
locale="de-DE"
(pivot-view-change)="onView($event)"
></pivot-report>
`
})
export class TicketReportComponent {
rows: object[] = [];
templates = [];
start: PivotView = { rows: ["Team"], values: [{ field: "Ticket", agg: "count" }] };
onView(event: Event) {
const { view } = (event as CustomEvent<{ view: PivotView }>).detail;
// store the view
}
}
Binding the stored view back as [defaultView] is fine: the element ignores the view it reported
itself. The report's React scheduler and ECharts' pointer listeners run in Angular's zone and may
trigger change detection (not measured); if that shows, create and feed the element outside the zone, as the element reference
shows how.
React pages¶
A React page can use pivotkit-react directly and save the element's second copy of React.
The element works in a React tree too: React 18 passes only attributes to a custom element, so set
data in a ref; React 19 sets properties and listens to onpivot-view-change. See
React on the page.
A taller report on phones¶
On a phone the zones sit above the table, so 640 px leave little room for it. Give the element more height on narrow screens:
pivot-report { height: 640px; }
@media (max-width: 639px) {
pivot-report { height: calc(100svh - 24px); }
}
<pivot-report height="100%"></pivot-report>
Next steps¶
- Element reference: every attribute, property and event, controlled and uncontrolled use, CSP and bundle size.
- Theming: the
--pk-*tokens, set on the element. - Use a report: what the people using it can do.