Embedding a third-party widget that reads from a schema
This recipe shows how to load an external JavaScript library alongside pagelove.mjs and have it read data from schema instances already present in the DOM. The pattern works with any library that renders from data — charting, mapping, rich text, data grids — as long as it can read from the DOM and re-render when the DOM changes.
The pattern
The approach relies on three moving parts:
- Schema instances in HTML — your data lives in the DOM as
[itemscope]elements with[itemprop]properties. The external library reads from these elements. pagelove.mjskeeps the DOM in sync — the Pagelove runtime applies server-side mutations to the DOM in real time via Server-Sent Events. The data elements stay current without any polling.- The external library re-renders when the DOM changes — either by observing the DOM directly with a
MutationObserver, or by listening for thePLMutationorPLMutationAppliedevents thatpagelove/sse.mjsdispatches ondocument.
Set up the data in HTML
Start with schema instances in the page. This example uses a SalesRecord type with month and revenue properties:
<table id="sales-data">
<tbody>
<tr itemscope itemtype="https://example.com/SalesRecord">
<td itemprop="month">January</td>
<td itemprop="revenue">42000</td>
</tr>
<tr itemscope itemtype="https://example.com/SalesRecord">
<td itemprop="month">February</td>
<td itemprop="revenue">51000</td>
</tr>
<tr itemscope itemtype="https://example.com/SalesRecord">
<td itemprop="month">March</td>
<td itemprop="revenue">47000</td>
</tr>
</tbody>
</table>
<canvas id="sales-chart" width="600" height="300"></canvas>
The data is visible in the page as a regular HTML table. The chart library will read from the same elements and render a visual representation alongside it.
Load the libraries
Load pagelove/sse.mjs for real-time sync and your chosen charting library. The order does not matter — the charting library reads from the DOM after both have loaded:
<script type="module" src="https://pagelove.github.io/beta-js/pagelove/sse.mjs"></script>
<script src="https://cdn.example.com/chart-library.min.js"></script>
Read data from the DOM
The charting library does not need to know about Pagelove. It reads from [itemprop] elements the same way any DOM-aware code would:
<script type="module">
function readSalesData() {
const rows = document.querySelectorAll('#sales-data [itemtype="https://example.com/SalesRecord"]');
const labels = [];
const values = [];
for (const row of rows) {
labels.push(row.querySelector('[itemprop="month"]').textContent);
values.push(Number(row.querySelector('[itemprop="revenue"]').textContent));
}
return { labels, values };
}
function renderChart(data) {
// Replace with your charting library's API.
// This is a conceptual sketch — the specifics depend on the library.
const canvas = document.getElementById('sales-chart');
ChartLibrary.render(canvas, {
type: 'bar',
labels: data.labels,
datasets: [{ label: 'Revenue', data: data.values }]
});
}
// Initial render
renderChart(readSalesData());
</script>
The readSalesData function walks the DOM, extracts values from [itemprop] elements, and returns a plain object. The rendering function passes that data to the charting library. This separation keeps the data-reading logic reusable.
Re-render on live changes
When another user (or another tab) updates a SalesRecord, pagelove/sse.mjs applies the mutation to the DOM and dispatches a PLMutationApplied event on document. Listen for that event and re-render the chart:
<script type="module">
document.addEventListener('PLMutationApplied', (event) => {
const { selector } = event.detail;
const salesTable = document.getElementById('sales-data');
if (salesTable && salesTable.querySelector(selector)) {
renderChart(readSalesData());
}
});
</script>
The listener checks whether the mutation's target selector falls inside the #sales-data table. If it does, the chart is re-rendered with the updated DOM values. Mutations to unrelated parts of the page are ignored.
Using PLMutation instead
If you want to act before the DOM change lands — to animate the transition, for example — listen for PLMutation instead of PLMutationApplied. The PLMutation event fires before the DOM is modified and is cancelable: calling event.preventDefault() skips the automatic DOM update, letting you apply the change yourself with whatever animation or transition the library supports.
Using MutationObserver as a fallback
If the page does not load pagelove/sse.mjs (because real-time sync is not needed), an external library can still detect changes made by other code using a standard MutationObserver:
const observer = new MutationObserver(() => {
renderChart(readSalesData());
});
observer.observe(document.getElementById('sales-data'), {
childList: true,
subtree: true,
characterData: true
});
This approach is library-agnostic and works whether the DOM changes come from Pagelove, from user input, or from any other script.
Choosing an event strategy
| Strategy | When to use |
|---|---|
PLMutationApplied |
The data changed on the server and you want to re-render after the DOM is updated. The most common choice for dashboards and live views. |
PLMutation |
You need to intercept the change before it lands — for custom animation, validation, or to cancel it. |
MutationObserver |
The page does not use pagelove/sse.mjs, or the chart should react to any DOM change regardless of source. |
See also
- Server-Sent Events — the
PLMutationandPLMutationAppliedevent reference. - Schema instances in HTML — how
[itemscope]and[itemprop]carry structured data.