Skip to main content

Petstore: Pets

The Pets page, served at /console/pets, is the petstore's main table. It lists every pet with server-side searching, sorting and paging, a ribbon of filters, row actions, multi-row selection with a bulk "Mark sold" action, and export. It is the best single page to read if you want to see what a server-mode Rich Table View looks like in a real app.

What this page shows​

The pets page: a server-mode table with paging, search, per-column filters and row actions

  • Server mode. The table posts a DataTables request to /console/pets/query. PetsRest.query runs it against the pet collection with DataTablesQuery.run, so searching and ordering are BeanQuery expressions evaluated on the server. A malformed search expression comes back as a 200 response carrying an error envelope, so the table can show the message instead of failing.
  • Ribbon. Two option groups ("Pet status" with Available and Pending, and "Species" with Mammals and Others) that set column filters, an "+ Add pet" dialog button, copy and CSV export (Excel and PDF when available) and a refresh button.
  • Row actions. Edit (a dialog form, merges the row on success) and Delete (with a confirmation).
  • Selection and bulk. Rows can be selected across pages, except sold pets, which carry the reason "Already sold." The bulk action "Mark sold" calls POST /console/pets/{id}/sell once per selected row.
  • URL state. The ids URL-state directive carries a comma-separated list of pet ids in the page URL. The query endpoint accepts it as ?ids= and narrows the rows to those ids before the column filters apply.
  • Polling. The table refreshes every 30 seconds and pauses while a cell is being edited.

Template​

The template is pets.ftlh. The excerpt keeps the data mode, the URL state and the bulk definition.

<@card type="datatables" id="pets" title="Pets">
{
rowType: 'Pet',
dataMode: 'server',
dataUrl: '/console/pets/query',
urlState: { ids: {} },
pollIntervalMs: 30000,
selection: { rowIdField: 'id', selectAll: true, scope: 'persistent', labelField: 'name',
selectableWhen: [ { field: 'status', op: 'ne', value: 'SOLD', reason: 'Already sold.' } ] },
bulk: { actions: [
{ id: 'sell', label: 'Mark sold', endpoint: '/console/pets/{id}/sell', method: 'POST', bulkMode: 'perRow',
confirmTitle: 'Mark pets sold', onSuccess: 'redraw' } ] }
}
</@card>

Builder twin​

PetsPageBuilders.pets builds the same table. Selection and bulk are two separate opt-ins; the bulk definition is created from a WritePermit and the selection it applies to.

var selection = SelectionDef.create("id").selectAll(true).scope(SelectionDef.Scope.PERSISTENT).labelField("name")
.selectableWhen(RowActionEnabledRule.of("status", Op.NE, "SOLD", "Already sold."));
TableSpec.create("pets").rowType("Pet").dataMode(DataMode.SERVER).dataUrl("/console/pets/query")
.urlState("ids").pollIntervalMs(30_000)
.selection(selection)
.bulk(BulkMutateDef.create(WritePermit.forCapability("pets.sell"), selection).actions(
RowAction.create("sell").label("Mark sold").endpoint("/console/pets/{id}/sell")
.method(Method.POST).bulkMode(BulkMode.PER_ROW).onSuccess(OnSuccess.REDRAW)));

The ?mixed variant​

Request /console/pets?mixed and the page is "half FTL": pets-mixed.ftlh lays out the page and places the table with <@card ref="pets"/>, while the table itself comes from PetsPageBuilders.petsMixed(). Both paths share one petsTable() method, so they cannot drift. Use this shape when you want a hand-written layout around a builder-defined card. ?builder renders the all-builder page, and ?builder&mixed renders the mixed one through the builder path.

Per-row and aggregate bulk​

The Pets bulk action is in perRow mode: the browser submits N independent single-row requests to the same sell endpoint, each with its own result. The other mode, aggregate, sends one request listing every selected id and expects one BulkResult back, with a per-id outcome. The request body is a BulkRequest: the ids, plus an idempotencyKey so a retried submit never double-applies. The petstore uses the aggregate shape on the Pending changes page, not here; see Petstore: Pending Changes. Selection and bulk mutation are introduced in Rich Table Views: Actions.

Source and tests​

Source: juneau-petstore/juneau-petstore-core, classes PetsRest and PetsPageBuilders (in the pages package), and the templates pets.ftlh, pets-mixed.ftlh and fragments/pets.ftlh.

Tests: PetsRest_Test (query paging and counts, ?ids=, sell and its 409 conflict), PetstorePages_ContractTest (the page contract, including the ids URL state) and PetstoreTwins_Test (template, builder and ?mixed agree).

See also​