Skip to main content

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.

TypeJavaBodyWhat the handler draws
kpinonelabel and dataUrlOne number from /console/dev/cards/stats/available, with its label
inventory-gaugeInventoryGaugeCardTypespecies, low, highA 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.

See also​