Petstore: Custom Cards
The Custom cards page (/console/dev/cards) shows how an adopter adds a card type of their own. It carries one
card type that needs no Java at all (kpi), one whose body the server validates and reshapes
(inventory-gauge), two ordinary chart cards fed by the same page, and two prose cards that exercise the card
API from a button row. Read it next to Building a Console Page when you
need a card the stock types do not cover.
Two ways to write a card type
A card type is a JavaScript handler registered under a type id. A Java class is optional, and is worth adding only when the server should validate or enrich the body before the page ships.
| Type | Java | Body | What the handler draws |
|---|---|---|---|
kpi | none | label and dataUrl | One number from /console/dev/cards/stats/available, with its label |
inventory-gauge | InventoryGaugeCardType | species, low, high | A gauge for one species, read from /console/dev/cards/inventory?species= |
Both handlers live in the page script petstore-cards.js, served beside CustomCardsRest. The page loads it
through init, and the handlers register by pushing onto window.JuneauConsoleCards. That queue works whether
your script runs before or after the console shell:
(window.JuneauConsoleCards = window.JuneauConsoleCards || []).push(['kpi', {
render(card, el, ctx) { ... }
}]);
With no registered Java handler, a type falls back to the generic passthrough: the JSON5 body becomes the card
fragment unchanged. That is all kpi is.
The validated type
InventoryGaugeCardType implements CardTypeHandler. Its toFragment rejects a body with no species, an
unknown species, a non-whole low or high, a negative low, or low at or above high. low defaults to 0
and high to 100. A good body is reshaped into a gauge object that adds the dataUrl for that species, which is
what the browser handler draws. A bad body fails the page at render time, so a typo fails loudly and does not
produce a broken gauge.
The host registers the type once, on the console's FreeMarker mixin (PetstoreConsolePage.freemarker()):
ConsoleFreemarkerMixin.create().basePath(TEMPLATES).chromeTemplate("base.ftlh")
.adopterAssets(PetstoreConsolePage.class.getClassLoader(), "org/apache/juneau/petstore")
.cardType(new InventoryGaugeCardType()).requirePacks("charts").build();
Reserved ids
A new type id must match ^[a-z][a-z0-9-]{0,31}$. These ids belong to the framework and cannot be registered:
html, datatables, terminal-view, terminal, chart and job-log. A bad name, a reserved id and a duplicate each
fail when the mixin is built. A JSON body that sets a base key (id, type, title, src, template, ref)
fails the page at render time, because those are set as attributes. kpi and inventory-gauge are free ids.
Template
<@page tab="dev/cards" init=["/console/dev/cards/petstore-cards.js"]>
<@card id="available" type="kpi" title="Pets available">{label:'Pets available', dataUrl:'/console/dev/cards/stats/available'}</@card>
<@card id="gauge" type="inventory-gauge" title="Dog inventory">{species:'DOG', low:20, high:120}</@card>
Builder twin
var gauge = CardSpec.of(InventoryGaugeCardType.TYPE, "gauge").title("Dog inventory")
.body("species", "DOG").body("low", 20).body("high", 120);
return PageSpec.create().tab("dev/cards").init("/console/dev/cards/petstore-cards.js")
.card(CardSpec.of("kpi", "available").title("Pets available")
.body("label", "Pets available").body("dataUrl", "/console/dev/cards/stats/available"))
.card(gauge)
...
PageSpec runs the gauge body through the same registered handler, so both pages carry the same validated
fragment. ?builder renders the twin.
The card API and the Store page
The toolbar card has buttons that call JuneauConsole.refreshCard and JuneauConsole.cardApi, and the script
listens for the juneau:card-mounted event to record mount order. The script also reads and writes its own
?state=species(CAT) directive for the gauge. The Store page loads the same script for its kpi alone; see
Petstore: Store.
The page's charts load their assets through the charts pack named in requirePacks; see
Card Asset Dependencies for how packs and init scripts are ordered.
Source: CustomCardsRest, InventoryGaugeCardType and petstore-cards.js in
juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/console/dev/; DevPageBuilders.cards
in .../console/pages/; cards.ftlh under .../console/templates/.
Tests: CustomCardsRest_Test, InventoryGaugeCardType_Test, PetstoreCustomCard_BrowserTest.