Entity Controller Pattern
Table of Contents
Summary
Every domain entity in ORE Studio's Qt UI is managed by a four-layer stack:
a controller that owns MDI windows and orchestrates NATS calls, a
list-window (EntityListMdiWindow) that displays a paginated table of
records, a detail-dialog (EntityDetailDialog) for create/edit/view, and
an abstract client model (AbstractClientModel) that fetches and caches
the entity's rows from the NATS service. This pattern is implemented by base
classes in ores.qt.api and followed consistently across all entity types.
Detail
The four layers
AbstractClientModel
- Subclass of
QAbstractTableModel. - Sends a NATS list request on
refresh()and populates its row cache on response. - Emits
dataReady()when the initial fetch completes. - Provides
entityAt(row)for the list-window to feed into the detail dialog. - Column metadata comes from
ColumnMetadatastructs defined per entity type.
EntityListMdiWindow
- MDI sub-window containing a
QTableViewbacked by anAbstractClientModel. - Toolbar: Refresh, New, Edit, View History, Delete (enabled/disabled by selection state and user permissions).
- Pagination via
PaginationWidget. - Selection changes propagate to the toolbar button states.
- Opened and closed by the controller; only one instance per entity type lives in the MDI area at a time.
EntityDetailDialog
- Modal dialog for create, edit, and read-only view.
- Opened from the list-window toolbar or via controller action.
- On accept: sends a NATS create or update request; on completion refreshes the list model.
- Change reason dialog (
ChangeReasonDialog) is injected fromChangeReasonCachefor entities requiring audit trail entries. - Temporal entities show bitemporal fields; non-temporal entities omit them.
EntityController
- Owns the model, list-window, and dialog lifecycles.
- Created by the plugin in
on_login; destroyed inon_logout. showListWindow()— opens or raises the MDI sub-window.ClientManagerreference held throughout the session for NATS calls.- Emits
statusMessagefor the status bar; connected by the plugin.
NATS wiring
All domain requests go through ClientManager, which wraps the NATS client
and provides typed request/response helpers. The general flow:
User action (button click)
→ EntityController::onNew/onEdit
→ EntityDetailDialog::exec()
→ on accept: ClientManager::send<Request>(payload)
→ NATS response callback (lambda on Qt event loop)
→ model->refresh() or emit statusMessage
Responses arrive asynchronously; lambdas captured in the controller marshal
back to the Qt thread via QMetaObject::invokeMethod(..., Qt::QueuedConnection)
where cross-thread signalling is needed.
History dialogs
For temporal entities a HistoryDialog shows the bitemporal record set.
Opened via the list-window toolbar's "View History" button. Uses the same
ClientManager infrastructure but sends a history-fetch request and
displays results in a read-only table.
Naming conventions
For an entity Foo:
| Layer | Class name | File |
|---|---|---|
| Controller | FooController |
FooController.{hpp,cpp} |
| List window | FooMdiWindow |
FooMdiWindow.{hpp,cpp} |
| Detail dialog | FooDetailDialog |
FooDetailDialog.{hpp,cpp} |
| Table model | ClientFooModel |
ClientFooModel.{hpp,cpp} |
| History dialog | FooHistoryDialog |
FooHistoryDialog.{hpp,cpp} |
All live in the domain plugin's include/ores.qt/ and src/ directories.
Item delegates
Complex cells (enum drop-downs, date pickers, colour swatches) use
QStyledItemDelegate subclasses. Common ones live in ores.qt.api
(EntityItemDelegate, ClientResultItemDelegate); entity-specific ones
live alongside the model.
See also
- Qt Plugin Architecture — how controllers are created/destroyed via the plugin lifecycle.
- ores.qt.api — the base classes (EntityController, AbstractClientModel, EntityListMdiWindow, EntityDetailDialog) that implement this pattern.