Petstore: Rendering Flavors
The Rendering flavors section (/console/dev/flavors) shows the same list of ten pets drawn by four different view
technologies, each inside the console chrome: the HTML5 bean builder, FreeMarker, Mustache and the standalone React
app. It replaces the earlier standalone view resources with pages inside the console. Each flavor is a level-3
subtab under Developer, so a reader can flip between them and compare the output.
Four subtabs
FlavorsRest (/console/dev/flavors) redirects / to the first flavor with a 303 to html. The enum Flavor
holds each flavor's id, nav label, caption and the GitHub link to its source. The nav tree in base.ftlh lists them
as the nodes html, freemarker, mustache and react under flavors.
| Subtab | Resource | What serves the list |
|---|---|---|
html | HtmlFlavorRest | A Table built with the HTML5 bean builder, serialized by HtmlSerializer |
freemarker | FreemarkerFlavorRest | The fragments/pets.ftlh template through the FreeMarker view bridge |
mustache | MustacheFlavorRest | A pets.mustache template through MustacheViewRenderer and a MustacheMixin |
react | FlavorsRest.react | The standalone app at /petstore-ui, embedded in an iframe |
All three server flavors draw the same rows. FlavorPets.rows returns the first ten pets by id with display-ready
strings (the price has two decimals), so the engines differ only in markup.
Chrome-less fragments
Each server flavor is a child resource that serves two things: its console page at /, and a bare fragment at
/fragment with no page chrome. The console page is one html card whose content comes from the fragment through
src=. The fragment is a <table class="petstore-flavor" data-flavor="...">, so a test can tell the engines apart
and compare their cells exactly. The FreeMarker fragment carries no <@page> for the same reason: it is loaded into
a card, not served as a page.
The React flavor has no fragment. Its page carries an iframe pointing at /petstore-ui, the same app that talks to
the /petstore API on its own. /petstore-ui is PetstoreUiResource, which is not part of the console.
Template
flavors.ftlh serves the three server flavors. The engine, label, caption and source attributes come from
Flavor.page():
<@page tab="dev/flavors/${engine}" toolkit="views">
<@card id="caption" title=label>
<div class="jc-prose"><p>${caption}</p><p><a href="${source}">View source</a></p></div>
</@card>
<@card id="flavor" title="Pets" src="/console/dev/flavors/${engine}/fragment"/>
</@page>
flavors-react.ftlh has the same caption card and replaces the last card with the iframe.
Builder twin
var p = PageSpec.create().tab("dev/flavors/" + flavor.id()).toolkit("views")
.html("caption", flavor.label(), "<div class=\"jc-prose\"><p>%s</p><p><a href=\"%s\">View source</a></p></div>"
.formatted(escapeHtml(flavor.caption()), escapeHtml(flavor.sourceUrl())));
if (flavor == Flavor.REACT)
return p.html("flavor", "React app", "<iframe src=\"/petstore-ui\" ... ></iframe>");
return p.htmlSrc("flavor", "Pets", "/console/dev/flavors/" + flavor.id() + "/fragment");
?builder renders this on every subtab.
Source: FlavorsRest, HtmlFlavorRest, FreemarkerFlavorRest, MustacheFlavorRest, Flavor and FlavorPets in
juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/console/dev/flavors/;
DevPageBuilders.flavor in .../console/pages/; flavors.ftlh, flavors-react.ftlh and
fragments/pets.ftlh under .../console/templates/, and pets.mustache under .../console/fragments/.
Tests: FlavorsRest_Test, PetstoreFlavors_BrowserTest.