Creating a template
A template is a JSON stored in templates.source. We don’t store any .tsx in the database, so a template change is just a record update, there is no build and no deploy. The reasoning is in issue #123 .
1. Which template
We have six registered templates, and the one we pick decides which data.<kind> block we have to write:
| Need | Template name | Block | State |
|---|---|---|---|
| A table of rows | PHPReactionReport/Widget/ListingTableTemplate | data.listing | Rendering |
A key: value block | PHPReactionReport/Widget/SummaryTemplate | data.summary | Rendering |
| A line chart | PHPReactionReport/Widget/GraphTemplate | data.graph | Rendering |
| Free HTML | PHPReactionReport/Widget/HTMLTemplate | data.html | Rendering |
| A meter | PHPReactionReport/Widget/AuxiliaryMeterTemplate | data.meter | PENDING |
| A gauge | PHPReactionReport/Widget/GaugeTemplate | data.gauge | PENDING |
The names are registered in src/utils/templates/templateName.ts and the components are mapped in src/utils/templates/widgetTemplates.tsx. The <kind> of the data block has to match the template, otherwise the validation rejects it.
2. The shape
Every template has the same three parts:
{
"schemaVersion": 1,
"template": {
"name": "PHPReactionReport/Widget/ListingTableTemplate",
"version": 1,
},
"display": {
"title": "Invoice Sells Report Listing",
"colSpan": 12,
"variant": "full",
},
"data": {
"listing": {},
},
}templateis the name, with or without the version."name"alone resolves to the latest. The version can also be written as a suffix (…Template:2).displayistitle,colSpan(1 to 12, where 12 is the full width) andvariant(fullorsidebox).datais one block only, named after the kind of the template.
The validation is strict, an unknown key at any level is rejected. If the widget doesn’t render, the descriptor is the first thing to check.
The full schema, with every accepted key, is on Template JSON schema.
3. The columns
There are two ways to declare the columns, and it depends on whether we know them or not.
We don’t know them, so we take everything the DP returns:
"columns": "$data"We know them, so we declare each one:
"columns": [
"market",
{ "name": "amt", "map": "$data.amount", "header": "Amount", "type": "money" }
]A bare string is the short form of { "name": "market", "map": "$data.market" }. The map is the field of the row, header is the label displayed, and type is the format (number, money, datetime, …).
$data alone means “all the fields of the result”. $data.<field> means “this field of the current row”. It is a single level, we don’t go inside a nested object with it.
4. Samples
These are the sample files of the project. They are the fastest way to start, copy one and change the columns.
4.1 Listing, columns we know
{
"schemaVersion": 1,
"template": {
"name": "PHPReactionReport/Widget/ListingTableTemplate",
"version": 1
},
"display": {
"title": "Invoice Sells Report Listing",
"colSpan": 12,
"variant": "full"
},
"data": {
"listing": {
"columns": [
{ "name": "Id", "map": "$data.Id", "header": "Id", "type": "number" },
{
"name": "Date",
"map": "$data.Date",
"header": "Date",
"type": "datetime"
},
{ "name": "Client", "map": "$data.Client", "header": "Client" },
{
"name": "nbProducts",
"map": "$data.nbProducts",
"header": "nbProducts",
"type": "number"
},
{
"name": "Subtotal",
"map": "$data.Subtotal",
"header": "Subtotal",
"type": "money"
}
]
}
}
}4.2 Listing, nested rows
For a parent / child result, like the categories. We take all the columns and we tell the template which fields build the tree:
{
"schemaVersion": 1,
"template": {
"name": "PHPReactionReport/Widget/ListingTableTemplate",
"version": 1
},
"display": { "title": "Categories", "colSpan": 12 },
"data": {
"listing": {
"columns": "$data",
"nested": {
"idField": "id",
"parentField": "parent_id",
"labelField": "name",
"defaultExpanded": false
}
}
}
}4.3 Summary
The summary ignores the columns and the headers, it displays the result of the DP as key: value rows, so the block stays empty:
{
"schemaVersion": 1,
"template": {
"name": "PHPReactionReport/Widget/SummaryTemplate",
"version": 1
},
"display": {
"title": "Invoice Sells Report Summary",
"colSpan": 6,
"variant": "full"
},
"data": {
"summary": {}
}
}4.4 Graph
The first column is the X axis and every other numeric column becomes a series. columns sets the order and headers names the series:
{
"schemaVersion": 1,
"template": {
"name": "PHPReactionReport/Widget/GraphTemplate",
"version": 1
},
"display": {
"title": "Invoice Sells Report Sells Per Day",
"colSpan": 12,
"variant": "full"
},
"data": {
"graph": {
"columns": ["date", "subtotal"],
"headers": { "date": "Date", "subtotal": "Subtotal" },
"period": "auto",
"aggregation": "sum",
"fillGaps": true
}
}
}4.5 HTML
The HTML can be static in the descriptor, or it can come from the DP result with a sourceMap:
{
"schemaVersion": 1,
"template": { "name": "PHPReactionReport/Widget/HTMLTemplate", "version": 1 },
"display": {
"title": "Rendered from DP",
"colSpan": 3
},
"data": {
"html": {
"sourceMap": "$data.html"
}
}
}4.6 The other samples
The rest of the files are in the repo, same folder:
| File | What it shows |
|---|---|
progressionListing.report.json | A listing with a progression |
auxiliaryResultsSummary.report.json | A summary built on the auxiliary results |
htmlStatic.report.json | The HTML written in the descriptor |
kpiMeter.report.json | The meter block, ready for when the wiring lands |
5. Testing the template
We don’t have to build a full report to see a template:
/templates/{id}/renderdisplays the template alone with the sample data of the descriptor. This is the fastest loop while we are writing it./dashboard-widgets/{id}/renderdisplays the widget with its real DP data and its template./dashboard-widgets/{id}/populatedisplays the data only. If the widget is empty, this one tells us if the problem is the data or the template.
More about these pages in Pages & Integration.
6. Related
- Template JSON schema: every accepted key
- Templates: the components behind each template
- Creating a report: the four API steps
- Pages & Integration: where to test what