Tables and Trees — Configuration-Driven Tables, Tree-Tables, and Views¶
Overview and Motivation¶
Tables are the workhorse of business applications: search results, pick lists, journal views,
editable detail grids. Plain JavaFX makes each of these a chore — every TableColumn needs a
cell-value factory, a cell factory, a formatter, an alignment, an editor, and none of it survives
a restart: column widths, ordering, visibility and sorting are gone when the user closes the
window.
Tentackle replaces that per-column boilerplate with a declarative table configuration: a single
TableConfiguration object describes the
table by binding paths, and everything else —
cell rendering, editors, formats, preferences-backed layouts, totals, printing, spreadsheet export —
derives from it:
- One configuration, two view types — the same
TableConfigurationconfigures both anFxTableViewand anFxTreeTableView. - Columns are binding paths —
config.addColumn("orgUnit", "Org Unit")is all it takes; the column's type, format, alignment and editor follow from the bound model member. - Rendering by value class — a pluggable
TableCellTypeknows how to display and edit one Java type, application-wide. - Layouts that roam — column widths, order, visibility and sorting are stored in the persisted preferences, per user or system-wide; with database-backed preferences the layout follows the user to any workstation.
- PDO awareness on top — the rich desktop client
derives default configurations from the PDO model, adds a column-chooser popup, printing, and
spreadsheet/XML export (
tentackle-fx-rdc-poiupgrades the export to real Excel workbooks).
The foundation lives in org.tentackle.fx.table (+ table.type) in this module; the PDO layer in
org.tentackle.fx.rdc.table of tentackle-fx-rdc
is covered below.
Key Concepts¶
The view components¶
FxTableViewandFxTreeTableViewextend the JavaFXTableView/TreeTableViewand implementFxComponent, so they participate in the binding and delegate machinery like any other Tentackle FX control. Each view holds its (optional but recommended)TableConfiguration.FxTableColumn/FxTreeTableColumnand the matchingFxTableCell/FxTreeTableCellare created from the column configurations — application code rarely instantiates them directly.TotalsTableViewdisplays the totals of another table (see below).
TableConfiguration — the single source of truth¶
A TableConfiguration<S> describes a
table of row type S. Its name doubles as the key to the table's preferences. Columns are
added by binding path:
TableConfiguration<Invoice> config = FxFactory.getInstance().createTableConfiguration(Invoice.class, null);
config.addColumn("invoiceNumber", bundle.getString("invoiceNumber"));
config.addColumn("total", bundle.getString("total"));
config.setEditMode(TableConfiguration.EDITMODE.ROW);
config.configure(tableView); // creates columns, cells, editors, applies preferences
configure(...) accepts either an FxTableView or an FxTreeTableView — the configuration is
view-type agnostic (for tree-tables it also creates the TreeItems via createTreeItem).
Two enums control the overall behavior:
BINDING— whether the cells are bound to the row object's members:YES(the default, binds all@Bindablemembers),INHERITED(includes bindables inherited from superclasses) orNO.EDITMODE—NO(read-only, the default),SIMPLE(edit the current cell only),ROW(commit moves to the next/previous cell in the row) orCOLUMN(commit moves to the cell below/above).
The configuration owns an FxTableBinder that
binds the cells to the row objects. Don't confuse it with the controller's binder, which binds a
data list to the table as a whole: binding properties needed by cell editors — the
DomainContext, for instance — must be applied to the table binder
(see binding.md).
TableColumnConfiguration — per-column tuning¶
Each column is described by a
TableColumnConfiguration<S,T>,
whose name is the binding path (and the column's preferences key). Everything a business grid
needs is a property, with null meaning "use the default derived from the column type or editor":
| Aspect | Properties |
|---|---|
| Display | displayedName, alignment, pattern (yields a DecimalFormat or DateTimeFormatter), blankZero |
| Editing | editable, editor (an FxComponent overriding the cell type's default), validChars / invalidChars, unsigned, scale, maxColumns, caseConversion, autoSelect |
| Behavior | summable (defaults to true for numeric types — see totals), cellType (overrides the type-derived cell type), binding |
The configuration creates and caches the actual TableColumn or TreeTableColumn on demand
(getTableColumn() / getTreeTableColumn()).
Table cell types — rendering per value class¶
A TableCellType<T> is a singleton that knows
how to display (updateItem) and edit (getEditor) values of one Java class in table and
tree-table cells. Cell types are produced on demand by the
TableCellTypeFactory and registered
with @TableCellTypeService, a
mapped service keyed by the value class —
so an application can add cell types for its own
datatypes or replace the provided ones.
The org.tentackle.fx.table.type package ships cell types for strings, numbers, booleans, enums,
BMoney,
I18NText, plain objects, and the full
date/time family (Date, Time, Timestamp, LocalDate, LocalTime, LocalDateTime,
Instant, OffsetTime, OffsetDateTime, ZonedDateTime), plus abstract bases
(AbstractTableCellType, AbstractDateTimeTableCellType) for custom types.
Table configuration providers¶
A TableConfigurationProvider
creates the TableConfiguration for one row class. Providers are registered via
@TableConfigurationProviderService
(again a @MappedService) and located through the
TableConfigurationProviderFactory,
which maps the row class leniently — a provider registered for a supertype or interface serves the
subclasses as well. This is how generic UI such as the RDC search dialog obtains the right columns
for any row type without knowing it in advance. In RDC applications the per-PDO
GuiProvider
is the table configuration provider (see below).
Preferences-backed layouts¶
TableConfiguration.savePreferences(...) / loadPreferences(...) persist and restore the visual
state of a table via the pluggable
persisted preferences:
- What is stored: per column the visibility, width, view position and sort type/order, plus the
table's preferred width and height. Restoring the sorting is optional (
setSortingIncluded, default off); restoring the view size can be turned off (setViewSizeIncluded, default on). - Where it is stored: under a node named after the configuration's name, an optional suffix (to distinguish several tables of the same type, e.g. per controller), and the effective locale — column headers translate, so layouts are locale-specific. Each column gets a child node keyed by its binding path.
- User vs. system scope: layouts save to the user's preferences by default; loading falls back to the system preferences when the user hasn't stored a layout yet. Saving to the system scope turns a layout into the default for all users.
In a Tentackle application the preferences are usually database-backed, so a user's table layouts follow them to any workstation — and system-wide defaults are rolled out by simply saving them once.
Totals¶
TotalsTableView is a small companion table
displayed below (or wherever the layout puts it) a regular FxTableView. Bound via
setBoundTable, it mirrors the bound table's columns — widths and visibility are bound, the header
and horizontal scrollbar are hidden via its own CSS — and renders one row per aggregate the
application supplies as items (one row for totals; three for totals, minimum and maximum; …).
Which columns show a total is controlled by the column configuration's summable property,
defaulting to true for numeric column types.
The RDC Layer: PDO-Aware Tables¶
The rich desktop client builds on this
foundation in org.tentackle.fx.rdc.table and makes tables of PDOs essentially free.
GuiProviders supply the configuration¶
Every PDO's GuiProvider
extends TableConfigurationProvider. The
DefaultGuiProvider
derives a default configuration from the PDO model via reflection: each @Persistent attribute
with a @Bindable getter becomes a column, displayed names come from the provider's resource
bundle. createTableView() then creates the table, applies the binding mode and asserts that all
columns are bound. A typical override picks and orders the columns explicitly, using the
generated attribute-name constants:
@Override
public TableConfiguration<Message> createTableConfiguration() {
TableConfiguration<Message> config = createEmptyTableConfiguration();
config.addColumn(Message.AN_MESSAGENUMBER, getBundle().getString(Message.AN_MESSAGENUMBER));
config.addColumn(Message.AN_WHEN, getBundle().getString(Message.AN_WHEN));
config.addColumn(Message.RN_ORGUNIT, getBundle().getString(Message.RN_ORGUNIT));
return config;
}
This is what the RDC search dialog
uses for every result table: guiProvider.createTableView() plus a table popup — no per-entity
table code at all.
RdcTableConfiguration
and RdcTableColumnConfiguration
are the PDO-aware configuration implementations, and
PdoTableCellType
registers a cell type for the PersistentDomainObject interface itself, so PDO-valued columns
(relations, for example) render out of the box.
TablePopup — the user's table menu¶
TablePopup
(created via Rdc.createTablePopup(table, preferencesSuffix, title)) attaches a context menu to
any table or tree-table:
- a column chooser with per-column visibility checkboxes and an auto adjust width action;
- print with a preview, via the
TablePrintercontroller; - export to spreadsheet and export to XML, each for all or only the selected rows;
- save / load preferences — the user's own layout, plus loading the system-wide default; saving the system layout is offered when the application runs with system-only preferences (an administrative mode), and the save items disappear entirely when the preferences are read-only;
- expand / collapse all for tree-tables.
TableUtilities — replaceable table services¶
TableUtilities
is the @Service singleton behind the popup's actions: creating default configurations from PDO
classes, printing, and exporting. The stock implementation writes RFC-4180 CSV for the
spreadsheet export — dependency-free. Adding the
tentackle-fx-rdc-poi module to the
path replaces the service with one that writes styled, typed Excel workbooks via Apache POI,
without touching application code.
How It Fits Together¶
A controller displaying a list of invoices in an editable table:
- The FXML declares an
FxTableView(or the code creates one viaguiProvider.createTableView()in an RDC application). - The table configuration — from a
TableConfigurationProvider, the PDO'sGuiProvider, or built inline — is applied withconfig.configure(tableView): columns, cell types, editors and formats appear, and the saved layout is restored from the preferences. - The controller's binder binds the data list to the table by name; the table's own binder moves values between cells and row objects, translating and validating through the same machinery as any form field.
Rdc.createTablePopup(...)adds the column chooser, printing, export, and layout save/load.- When the user rearranges columns and saves, the layout lands in the (database-backed) preferences and greets them on every workstation from then on.
Package Layout¶
| Package | Content | Module |
|---|---|---|
org.tentackle.fx.table |
Configurations, providers, cell/column classes, TotalsTableView |
tentackle-fx |
org.tentackle.fx.table.type |
Built-in TableCellTypes (strings, numbers, money, date/time, …) |
tentackle-fx |
org.tentackle.fx.rdc.table |
PDO-aware configuration, TablePopup, TablePrinter, TableUtilities |
tentackle-fx-rdc |
org.tentackle.fx.rdc.poi |
Excel export replacing the CSV default | tentackle-fx-rdc-poi |
Related Documentation¶
- tentackle-fx — the extended JavaFX layer this builds on.
- Binding — the binding machinery behind columns and cells.
- tentackle-fx-rdc — the rich desktop client.
- Spreadsheet (XLS) Export — Excel export via Apache POI.
- Preferences — the persisted preferences backing table layouts.
- Embedded Entities vs. DataTypes — custom value types that may need their own cell type.