Bands
Intent
A definition describes bands, not pages. The report header, the group bands, the detail band, and the footers flow in walk order. Each target decides where that flow lands on its own surface. A definition that described its own page breaks would stop being portable across page sizes and across targets that have no pages at all.
Design decisions
The band is the unit of layout. Bands flow vertically in walk order on every target. Group nesting adds no indentation. What differs between targets is only where they cut the flow.
A group band has no current row. The report header, the group bands and
empty all leave @ unbound, so they read through a group handle or through
$. A band that needs the whole row it grouped by declares by: "=@".
empty replaces the body, not a row. It renders once when no rows remain,
and it takes the place of the whole group and detail walk, table structure
included.
Flow spacing is two style names, not a box. spaceBefore and spaceAfter
open gaps between band items. Padding insets a box, which is a different thing,
and the two aren’t interchangeable.
A height-declared report header pins from the page top. Declaring a height fixes the band’s box, so the next band starts at the pin whether the items filled it or not. That’s what keeps a letterhead the same size on every document.
Page bands are furniture, and only a paginating target draws them. They cross the seam as closures the target calls once it knows the page numbers. A target that ignores them never evaluates them at all.
Page columns are document structure, not page geometry. A definition declares a columned region on the report or on a group, and it flows into strips. The page size stays a host option.
API walkthrough
Opening and closing the document
header is the report header band, rendered once before any row. It takes
an item array, or an object pinning a height.
{
"header": {
"height": 120,
"items": [
{ "type": "text", "value": "{{ $.input.company }}", "style": { "size": 14, "bold": true } }
]
}
}Every item in this band states its own size. Targets give the band a headline
default of bold 14pt, sitting above the document’s own style, so an item
declaring no size renders headline-sized. An exact "bold": false declares
that default away.
A pinned header requires page.margin on the document, because the pin
starts at the physical page edge.
footer is the report footer band, rendered once after the last group
closes. Totals for the whole document belong here, since the detail band’s own
total block draws once per table.
Grouping the body
groups is an array of group bands, outermost first. Each instance renders
its header, recurses into nested groups or the detail band, then renders its
footer.
break: "page" starts every instance on a fresh page. An instance already
at a page top doesn’t force an empty one. reset: "page" does the same and
restarts the page numbering for that instance.
Unpaginated targets map a break to their own vocabulary. The HTML target adds a class for your print stylesheet rather than ignoring it.
Drawing the rows
detail is the per-row band. Given an item array it draws stacked bands and
repeats them per row. Given a TableDetail it emits a real table. That reaches
HTML as thead and tbody, a PDF as a header row repeated on each page, and a
workbook as a frozen pane.
Saying nothing, deliberately
empty renders once when no rows remain after data, where, the report
sort and the report take.
{
"data": "$.orders[*]",
"empty": [{ "type": "text", "value": "No orders for {{ $.params.year }}", "style": { "size": 10 } }],
"detail": { "columns": [{ "header": { "value": "Order" }, "value": "{{ @.number }}" }] }
}Report aggregates are available, because they’re computed before the engine chooses the band. The report header and footer still render around it. Groups, detail and running accumulators don’t run.
An empty array is a deliberate declaration that the body renders nothing. With
empty absent, an ungrouped empty table keeps its structure and a grouped one
emits nothing, since no instance opens.
Furnishing the page
Paginated targets draw page.header and page.footer once per page.
Their scope is the report root plus page.number and page.total, so a band
mixes report data with page position. @ stays unbound.
page.margin is the inset from the physical page edge, one number for all
four sides. It’s the one piece of page geometry a document may declare, and
declaring it here and on the target too is a render error.
{
"page": {
"footer": [
{ "type": "text", "value": "Page {{ page.number }} of {{ page.total }}",
"style": { "size": 8, "align": "right" } }
],
"margin": 54
}
}Page bands are the one part of a definition validate can’t vouch for. Only
a target that paginates evaluates them, so a page band that throws fails the
PDF render and goes unnoticed everywhere else. Rendering a paginated target
is the only thing that exercises one.
Flowing in columns
columns flows a node’s content in that many vertical strips. It fills one
to its foot before moving to the next, and turns the page only once the last
runs out. Declare it on the report to column the body, or on a group to column
each instance. It takes an integer of two or more, and columned regions don’t
nest.
The declaring node’s own bands stay full width above and below the strips.
Spacing the flow
spaceBefore and spaceAfter open blank space before or after a band
item, in points. Adjacent values add. spaceBefore drops at a fresh page or
strip top, so a band doesn’t start a page indented, while page-band items keep
theirs.
They’re legal on item arrays including band images and splits. On a table
cell, a row’s style, a header, a total or a split slot they’re a definition
error. The XLSX target doesn’t read them, a grid having no flow.