Regions
A region is an empty, framework-owned container whose body is drawn by a named JavaScript function (a populator) instead of server-emitted markup. The framework owns the container, its identity, its lifecycle and its cancellation; the populator owns everything inside it.
There are exactly three region types, the RegionDef constants:
| Constant | Where it lives |
|---|---|
TYPE_ROW_DETAIL (row-detail) | Inside a table row's expandable detail panel. |
TYPE_CARD_BODY (card-body) | In the body of a card. |
TYPE_TAB_BODY (tab-body) | In a tab's body; lazy by default. |
Declaring a region
RegionDef (in org.apache.juneau.console.views) is the server-side descriptor. RegionDef.CONTRACT_VERSION
is "1" and is independent of the view, action, bulk and slot versions.
| Field | Meaning |
|---|---|
id | Unique within the host. |
populate | Name of the client populator, or null for the built-in default. A name, not a function, so a server-side allowlist can constrain it. |
type | One of the three types above. Usually set by the enclosing host. |
dataUrl | Same-origin path the declarative default fetches from. null means no declared fetch. |
params | Request parameters appended to dataUrl. Values may not be nested maps and keys may not collide with dataUrl's own query string. |
renderer | The JuneauViews.helpers function the default populate paints with, for example field-grid. |
lazy | Defer the first populate until visible. Defaults to false for row-detail and card-body, true for tab-body (effectiveLazy()). |
refreshMs | Auto-refresh interval. A value below RegionDef.MIN_REFRESH_MS is clamped up to it (5000 ms on the client). |
fields | The field catalog for card and tab regions (RegionDef.Field: label, render, href, format, span, actions). A row-detail region must not declare one. |
titleFields | Title keys the chrome's title painter may read. Blank entries are rejected. |
allowedPopulators | Extra populator names allowed in addition to the built-ins. |
config | Structured configuration owned by the populator: nested maps and lists are allowed. It is never appended to dataUrl or sent to the server, and is emitted only when non-empty, so a region without it has the same sidecar as before. The populator reads it as ctx.declared.config. |
RegionDef.validate() is fail-closed. It rejects a blank id, a disallowed or blank populator, an unknown type, a
cross-origin dataUrl, a nested-map param, a colliding param key, a config that isn't JSON-serializable, a blank title field, and a field catalog on a
row-detail region or an empty one where field-grid requires it. Field renderers must be built-in ids; see
Security Model.
Emitting a region
RegionTable.of(RegionDef) produces the empty container <div class='juneau-region'> carrying identity, type and
the handshake stamp. RegionTable.sidecar(hostId, regions) emits the per-host JSON sidecar; a row-detail host
whose subtree is cloned per row uses RegionTable.detailSidecar(regions), which has no id to avoid collisions
across open panels. In a row-detail region pass no CSRF token, since the enclosing table already stamped one.
RegionTable.of(region, csrfToken) stamps the token into the response, so a host that uses it must call
RequestBoundaryFilter.markTokenBearing(response) (Cache-Control: private, no-store); otherwise a shared cache can
serve one session's token to another. See REST Security.
Writing a populator
JuneauViews.regions.register("pet-history", function populate(ctx, container) {
// container is empty, in the document, and yours.
// Return nothing, a cleanup function, a Promise, or a Promise of a cleanup function.
const p = document.createElement("p");
p.textContent = "Loaded " + ctx.declared.dataUrl;
container.append(p);
return function cleanup() { /* unsubscribe */ };
});
The ctx carries an abort signal that fires on teardown, a scoped emit and on for the page-wide message bus,
and the declared values from the RegionDef (ctx.declared, with config present only when the region
declares one). A region sees only its own ctx, so the framework can sweep all of a
region's subscriptions when it tears down. A page with no regions pays nothing; the bus is created at first
enrolment.
The runtime requires AbortController, and refuses to populate when it is missing rather than degrading without a
signal.
A declared region may name a populator only if the name is a built-in or is listed in allowedPopulators.
In an author-written page slot, JuneauViews.regions.mount(hookup) takes a map of element id to populator name
and enrols one region per entry:
JuneauViews.regions.mount({ "history-slot": "pet-history" });
It returns a thenable of the region handles. A missing element id, a blank name or an unregistered populator fails loudly and enrols nothing.
The default populator
A region with no populate uses the built-in default, which fetches dataUrl (with params) and paints the
result through the named renderer, typically field-grid with the declared fields. This is the declarative
path: no JavaScript required.
The terminal-view and terminal populators
terminal-view and terminal are built-in populators that are not allowed by default. Each draws a live pane that
runs its own polling loop, so it ignores the region's dataUrl and refreshMs. TerminalViewDef.toRegion() and
TerminalDef.toRegion() opt in for you; a RegionDef you build yourself must call allowPopulators("terminal-view")
or allowPopulators("terminal"). See Terminal View Region and
Terminal Region.
The job-log populator
job-log runs a job for the row whose detail panel holds the region: a small form, a key fetched for that row, and
the job's streamed log. It comes from the console's joblog asset pack (juneau-joblog.js), not from the views
runtime, so it is a console-page feature. Its config is the job-log card's body plus submit.keyUrl, checked by
JobLogChecks. JobLogRegion.toRegion() builds the region with type row-detail, populate job-log, the
populator allowed, and the config; a datatables card declares the same thing as detail.region with
populate: 'job-log'. See Job-Log Card and
Row Jobs.
Polling while a panel runs a job
A table that polls redraws its rows on every tick, and a redraw closes any open detail panel. So while an expanded
panel hosts a row job that is running (or still being submitted), the timer's reload is held for that table and the
staleness pill shows Paused. This applies to every view, whether or not it sets pausePollingWhileEditing. Only the
timer is held: a user reload, a sort, a filter or a page change still redraws, and re-expanding the row re-attaches
the region to the running job with its log replayed from the start. A collapsed row's job and a job on a row that
isn't drawn don't hold the poll. Neither does a row action's job, even with its row's panel open: it streams into the
row, not the panel. When the job ends or the panel collapses, the next tick redraws as usual.