1. EntityPanel
The EntityPanel is the base UI class for working with entity instances. It usually consists of an EntityTablePanel, an EntityEditPanel, and a set of detail panels representing the entities having a master/detail relationship with the underlying entity.
1.1. Basics
You can either extend the EntityPanel class or instantiate one directly, depending on your needs.
public class AddressPanel extends EntityPanel {
public AddressPanel(SwingEntityModel addressModel) {
super(addressModel, new AddressEditPanel(addressModel.editModel()));
}
}
SwingEntityModel addressModel =
new SwingEntityModel(Address.TYPE, connectionProvider);
EntityPanel addressPanel =
new EntityPanel(addressModel,
new AddressEditPanel(addressModel.editModel()));
1.2. Detail panels
Adding a detail panel is done with a single method call, but note that the underlying EntityModel must contain the correct detail model for the detail panel, in this case a CustomerModel instance, see detail models. See EntityApplicationPanel.
public class CustomerPanel extends EntityPanel {
public CustomerPanel(SwingEntityModel entityModel) {
super(entityModel);
SwingEntityModel addressModel =
entityModel.detailModels().get(Address.TYPE);
detailPanels().add(new AddressPanel(addressModel));
}
}
2. EntityEditPanel
The EntityEditPanel manages the input components (text fields, combo boxes and such) for editing an entity instance.
When extending an EntityEditPanel you must implement the initializeUI() method, which initializes the edit panel UI. The EntityEditPanel class exposes methods for creating input components and binding them with the underlying EntityEditModel instance.
public class CustomerEditPanel extends EntityEditPanel {
public CustomerEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
//the firstName field should receive the focus whenever the panel is initialized
focus().initial().set(Customer.FIRST_NAME);
createTextField(Customer.FIRST_NAME);
createTextField(Customer.LAST_NAME);
createTextField(Customer.EMAIL);
createCheckBox(Customer.ACTIVE);
setLayout(new GridLayout(4, 1));
//the addInputPanel method creates and adds a panel containing the
//component associated with the attribute as well as a JLabel with the
//property caption as defined in the domain model
addInputPanel(Customer.FIRST_NAME);
addInputPanel(Customer.LAST_NAME);
addInputPanel(Customer.EMAIL);
addInputPanel(Customer.ACTIVE);
}
}
public class AddressEditPanel extends EntityEditPanel {
public AddressEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
focus().initial().set(Address.STREET);
createTextField(Address.STREET).columns(25);
createTextField(Address.CITY).columns(25);
createCheckBox(Address.VALID);
setLayout(new GridLayout(3, 1, 5, 5));
addInputPanel(Address.STREET);
addInputPanel(Address.CITY);
addInputPanel(Address.VALID);
}
}
public class CustomerAddressEditPanel extends EntityEditPanel {
public CustomerAddressEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
focus().initial().set(CustomerAddress.ADDRESS_FK);
createComboBoxPanel(CustomerAddress.ADDRESS_FK, this::createAddressEditPanel)
.preferredWidth(280)
.includeAddButton(true);
setLayout(borderLayout());
addInputPanel(CustomerAddress.ADDRESS_FK);
}
private AddressEditPanel createAddressEditPanel() {
return new AddressEditPanel(new SwingEntityEditModel(Address.TYPE, editModel().connectionProvider()));
}
}
2.1. Detailed example
Here’s how a text field is created and added to the edit panel.
createTextField(Customer.FIRST_NAME)
.columns(12);
setLayout(gridLayout(1, 1));
addInputPanel(Customer.FIRST_NAME);
And here’s the equivilent code, showing what’s going on behind the scenes.
ColumnDefinition<String> firstNameDefinition =
editModel().entityDefinition().columns().definition(Customer.FIRST_NAME);
//create the text field
JTextField firstNameField = new JTextField();
firstNameField.setColumns(12);
firstNameDefinition.description()
.ifPresent(firstNameField::setToolTipText);
//associate the text field with the first name attribute
component(Customer.FIRST_NAME).set(firstNameField);
//wrap the text field in a ComponentValue
ComponentValue<String, JTextField> firstNameFieldValue =
new AbstractTextComponentValue<String, JTextField>(firstNameField) {
@Override
protected String getComponentValue() {
return component().getText();
}
@Override
protected void setComponentValue(String text) {
component().setText(text);
}
};
//link the component value to the attribute value in the editor
firstNameFieldValue.link(editModel().editor().value(Customer.FIRST_NAME));
//create the first name label
JLabel firstNameLabel = new JLabel(firstNameDefinition.caption());
//associate the label with the text field
firstNameLabel.setLabelFor(firstNameField);
//create an input panel, with the label and text field
JPanel firstNamePanel = new JPanel(borderLayout());
firstNamePanel.add(firstNameLabel, BorderLayout.NORTH);
firstNamePanel.add(firstNameField, BorderLayout.CENTER);
setLayout(gridLayout(1, 1));
add(firstNamePanel);
2.2. Input controls
2.2.1. Boolean
JCheckBox checkBox =
createCheckBox(Demo.BOOLEAN)
.build();
NullableCheckBox nullableCheckBox =
(NullableCheckBox) createCheckBox(Demo.BOOLEAN)
.nullable(true)
.build();
JComboBox<Item<Boolean>> comboBox =
createBooleanComboBox(Demo.BOOLEAN)
.build();
2.2.2. Foreign key
EntityComboBox comboBox =
createComboBox(Demo.FOREIGN_KEY)
.build();
EntitySearchField searchField =
createSearchField(Demo.FOREIGN_KEY)
.build();
//readOnly
JTextField textField =
createTextField(Demo.FOREIGN_KEY)
.build();
2.2.3. Temporal
TemporalField<LocalDateTime> textField =
(TemporalField<LocalDateTime>) createTextField(Demo.LOCAL_DATE)
.build();
TemporalField<LocalDate> localDateField =
createTemporalField(Demo.LOCAL_DATE)
.build();
TemporalFieldPanel<LocalDate> temporalPanel =
createTemporalFieldPanel(Demo.LOCAL_DATE)
.build();
2.2.4. Numerical
NumberField<Integer> integerField =
(NumberField<Integer>) createTextField(Demo.INTEGER)
.build();
integerField =
createIntegerField(Demo.INTEGER)
.build();
NumberField<Long> longField =
(NumberField<Long>) createTextField(Demo.LONG)
.build();
longField =
createLongField(Demo.LONG)
.build();
NumberField<Double> doubleField =
(NumberField<Double>) createTextField(Demo.DOUBLE)
.build();
doubleField =
createDoubleField(Demo.DOUBLE)
.build();
NumberField<BigDecimal> bigDecimalField =
(NumberField<BigDecimal>) createTextField(Demo.BIG_DECIMAL)
.build();
bigDecimalField =
createBigDecimalField(Demo.BIG_DECIMAL)
.build();
2.2.5. Text
JTextField textField =
createTextField(Demo.TEXT)
.build();
JFormattedTextField maskedField =
createMaskedTextField(Demo.FORMATTED_TEXT)
.mask("###:###")
.valueContainsLiteralCharacters(true)
.build();
JTextArea textArea =
createTextArea(Demo.LONG_TEXT)
.rowsColumns(5, 20)
.build();
TextFieldPanel inputPanel =
createTextFieldPanel(Demo.LONG_TEXT)
.build();
2.2.6. Selection
DefaultComboBoxModel<String> comboBoxModel =
new DefaultComboBoxModel<>(new String[] {"One", "Two"});
JComboBox<String> comboBox =
createComboBox(Demo.TEXT, comboBoxModel)
.editable(true)
.build();
2.3. Panels & labels
JLabel label = createLabel(Demo.TEXT)
.build();
JPanel inputPanel = createInputPanel(Demo.TEXT)
.label(new JLabel("Label"))
.build();
2.4. Advanced Patterns
2.4.1. Configuration Options
EntityEditPanel supports configuration via a lambda in the constructor:
class InvoiceEditPanel extends EntityEditPanel {
public InvoiceEditPanel(SwingEntityEditModel editModel) {
super(editModel, config ->
// Keep displaying newly inserted invoice since we'll continue
// working with it by adding invoice lines
config.clearAfterInsert(false));
}
@Override
protected void initializeUI() {
// UI setup
}
}
2.4.2. Focus Management
Configure the focus behaviour:
class CustomerEditPanel extends EntityEditPanel {
public CustomerEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
focus().initial().set(Customer.FIRSTNAME);
focus().afterInsert().set(Customer.ADDRESS);
// Create your input components...
}
}
2.4.3. Inline Edit Panels with ComboBoxPanel
Create combo boxes with inline add/edit capabilities:
class TrackEditPanel extends EntityEditPanel {
public TrackEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
createComboBoxPanel(Track.MEDIATYPE_FK, this::createMediaTypeEditPanel)
.preferredWidth(160)
.includeAddButton(true)
.includeEditButton(true);
createSearchFieldPanel(Track.MEDIATYPE_FK, this::createMediaTypeEditPanel)
.preferredWidth(160)
.includeAddButton(true)
.includeEditButton(true);
}
private EntityEditPanel createMediaTypeEditPanel() {
return new MediaTypeEditPanel(new SwingEntityEditModel(Chinook.MediaType.TYPE, editModel().connectionProvider()));
}
}
2.4.4. Custom Component Integration
Replace default components with custom ones:
class TrackEditPanel extends EntityEditPanel {
public TrackEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
// Create a custom component
DurationComponentValue durationValue = createDurationComponent();
// Link it to the attribute value
editModel().editor().value(Track.MILLISECONDS).link(durationValue);
// And set the component it as the attribute component
component(Track.MILLISECONDS).set(durationValue.component());
}
private DurationComponentValue createDurationComponent() {
// Custom implementation
return new DurationComponentValue();
}
}
2.4.5. Keyboard Shortcuts and Actions
Add custom keyboard shortcuts for enhanced productivity:
class CustomerEditPanel extends EntityEditPanel {
public CustomerEditPanel(SwingEntityEditModel editModel) {
super(editModel);
}
@Override
protected void initializeUI() {
createTextField(Customer.STATE)
.keyEvent(KeyEvents.builder()
.keyCode(VK_SPACE)
.modifiers(CTRL_DOWN_MASK)
.action(Control.action(this::selectStateFromExistingValues)));
}
private void selectStateFromExistingValues(ActionEvent event) {
JTextField stateField = (JTextField) event.getSource();
Dialogs.select()
.list(editModel().connection().select(Customer.STATE))
.owner(stateField)
.select()
.single()
.ifPresent(stateField::setText);
}
}
2.4.6. Detail Panel Integration
EntityEditPanel can include detail panels for master-detail relationships:
class InvoiceEditPanel extends EntityEditPanel {
private final EntityPanel invoiceLinePanel;
public InvoiceEditPanel(SwingEntityEditModel editModel, SwingEntityModel invoiceLineModel) {
super(editModel, config -> config.clearAfterInsert(false));
this.invoiceLinePanel = createInvoiceLinePanel(invoiceLineModel);
}
@Override
protected void initializeUI() {
// Initialize main edit controls...
// Add detail panel
add(invoiceLinePanel, BorderLayout.SOUTH);
}
private EntityPanel createInvoiceLinePanel(SwingEntityModel invoiceLineModel) {
// Create and return invoice line panel
return new EntityPanel(invoiceLineModel);
}
}
3. EntityTablePanel
The EntityTablePanel provides a table view of entities.
3.1. Adding a print action
The most common place to add a custom control is the table popup menu, i.e. an action for printing reports or for acting on the selected rows. For the simplest case, where a single print action is required, a custom control can be associated with the PRINT ControlKey, this control will appear in the Print submenu in the table popup menu as well as on the table toolbar.
For more complex cases, where multiple print controls are required, custom controls can be associated with the PRINT_CONTROLS ControlKey.
public class CustomerTablePanel extends EntityTablePanel {
public CustomerTablePanel(SwingEntityTableModel tableModel) {
super(tableModel);
// associate a custom Control with the PRINT control key,
// which calls the viewCustomerReport method in this class,
// enabled only when the selection is not empty
control(PRINT).set(Control.builder()
.command(this::viewCustomerReport)
.caption("Customer report")
.smallIcon(FrameworkIcons.instance().print())
.enabled(tableModel().selection().empty().not())
.build());
}
private void viewCustomerReport() {
List<Entity> selectedCustomers = tableModel().selection().items().get();
if (selectedCustomers.isEmpty()) {
return;
}
Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
Map<String, Object> reportParameters = new HashMap<>();
reportParameters.put("CUSTOMER_IDS", customerIds);
JasperPrint customerReport = tableModel().connection()
.report(Customer.REPORT, reportParameters);
Dialogs.builder()
.component(new JRViewer(customerReport))
.owner(this)
.modal(false)
.title("Customer Report")
.size(new Dimension(800, 600))
.show();
}
}
4. EntityPanel.Builder
Use the EntityPanel.Builder class to specify a EntityPanel class configuration, for panels that should not be initialized until used, such the lookup table panels.
private static List<EntityPanel.Builder> createLookupPanelBuilders() {
EntityPanel.Builder addressPanelBuilder = EntityPanel.builder()
.entityType(Address.TYPE)
.panel(connectionProvider -> {
SwingEntityModel addressModel =
new SwingEntityModel(Address.TYPE, connectionProvider);
return new EntityPanel(addressModel,
new AddressEditPanel(addressModel.editModel()));
});
return List.of(addressPanelBuilder);
}
5. EntityApplicationPanel
The EntityApplicationPanel class serves as the main application UI. When extending this class you must provide a constructor with a single application model parameter, as seen below.
public class StoreApplicationPanel extends EntityApplicationPanel<StoreApplicationModel> {
public StoreApplicationPanel(StoreApplicationModel applicationModel) {
super(applicationModel, createPanels(applicationModel), createLookupPanelBuilders());
}
private static List<EntityPanel> createPanels(StoreApplicationModel applicationModel) {
CustomerModel customerModel = (CustomerModel)
applicationModel.entityModels().get(Customer.TYPE);
CustomerAddressModel customerAddressModel = (CustomerAddressModel)
customerModel.detailModels().get(CustomerAddress.TYPE);
EntityPanel customerPanel = new EntityPanel(customerModel,
new CustomerEditPanel(customerModel.editModel()),
new CustomerTablePanel(customerModel.tableModel()));
EntityPanel customerAddressPanel = new EntityPanel(customerAddressModel,
new CustomerAddressEditPanel(customerAddressModel.editModel()));
customerPanel.detailPanels().add(customerAddressPanel);
return List.of(customerPanel);
}
private static List<EntityPanel.Builder> createLookupPanelBuilders() {
EntityPanel.Builder addressPanelBuilder = EntityPanel.builder()
.entityType(Address.TYPE)
.panel(connectionProvider -> {
SwingEntityModel addressModel =
new SwingEntityModel(Address.TYPE, connectionProvider);
return new EntityPanel(addressModel,
new AddressEditPanel(addressModel.editModel()));
});
return List.of(addressPanelBuilder);
}
public static void main(String[] args) {
Locale.setDefault(new Locale("en", "EN"));
EntityPanel.Config.TOOLBAR_CONTROLS.set(true);
ReferentialIntegrityErrorHandling.REFERENTIAL_INTEGRITY_ERROR_HANDLING
.set(ReferentialIntegrityErrorHandling.DISPLAY_DEPENDENCIES);
EntityApplicationPanel.builder(StoreApplicationModel.class, StoreApplicationPanel.class)
.domain(Store.DOMAIN)
.applicationName("Store")
.defaultUser(User.parse("scott:tiger"))
.start();
}
}
6. EntitySearchField
The EntitySearchField is a powerful UI component for entity selection through text-based searching.
It extends HintTextField
and provides a search interface that triggers on ENTER key, displaying results based on the configured search criteria.
6.1. Overview
EntitySearchField
provides:
-
Text-based entity searching with automatic result handling
-
Single or multi-entity selection
-
Customizable result selection UI (list, table, or custom)
-
Optional add/edit functionality for creating or modifying entities
-
Search progress indication (wait cursor or progress bar)
-
Automatic search on focus loss (optional)
-
Keyboard shortcuts for add/edit operations
6.2. Basic Usage
EntitySearchModel searchModel = EntitySearchModel.builder()
.entityType(Customer.TYPE)
.connectionProvider(connectionProvider)
.searchColumns(List.of(Customer.FIRSTNAME, Customer.EMAIL))
.build();
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.multiSelection()
.columns(20)
.build();
6.3. Search Behavior
The search field operates as follows:
-
User types search text and presses ENTER
-
If the search returns:
-
No results: A message dialog is shown
-
Single result: That entity is automatically selected
-
Multiple results: A selection dialog appears
-
6.4. Customization Options
6.4.1. Custom Selectors
The default selector uses a list for result selection. You can provide custom selectors:
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.multiSelection()
.selectorFactory(new CustomerSelectorFactory())
.build();
6.4.2. Add and Edit Controls
Enable inline entity creation and editing:
SwingEntityEditModel editModel = new SwingEntityEditModel(Customer.TYPE, connectionProvider);
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.singleSelection()
.editPanel(() -> new CustomerEditPanel(editModel))
.confirmAdd(true) // Confirm before adding
.confirmEdit(true) // Confirm before editing
.build();
// Access controls
searchField.addControl(); // INSERT key by default
searchField.editControl(); // CTRL+INSERT by default
6.4.3. Search Indicators
Configure how search progress is displayed:
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.multiSelection()
.searchIndicator(SearchIndicator.PROGRESS_BAR)
.build();
6.4.4. Field Configuration
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.singleSelection()
.columns(20) // Field width
.upperCase(true) // Force uppercase
.searchHintEnabled(true) // Show "Search..." hint
.searchOnFocusLost(true) // Auto-search when focus lost
.selectionToolTip(true) // Show selection as tooltip
.editable(false) // Make read-only
.stringFactory(entity -> // Custom display text
entity.get(Customer.LASTNAME) + " - " + entity.get(Customer.CITY))
.separator(" | ") // Multi-selection separator
.build();
6.5. Search Control
You can trigger searches programmatically:
EntitySearchField searchField = EntitySearchField.builder()
.model(searchModel)
.multiSelection()
.build();
// Get search control
Control searchControl = searchField.searchControl();
// Use in toolbar or menu
Controls.builder()
.control(searchControl)
.build();
6.6. Advanced Features
6.6.1. Component Value Integration
A EntitySearchField
based ComponentValue
can be created via buildValue():
SwingEntityEditModel editModel = new SwingEntityEditModel(Invoice.TYPE, connectionProvider);
ComponentValue<Entity, EntitySearchField> searchFieldValue =
EntitySearchField.builder()
.model(searchModel)
.singleSelection()
.buildValue();
EntitySearchField searchField = searchFieldValue.component();
// React to selection changes
searchField.model().selection().entities().addConsumer(selectedEntities ->
System.out.println("Selected: " + selectedEntities));
// Link to edit model
editModel.editor().value(Invoice.CUSTOMER_FK).link(searchFieldValue);
6.6.2. Custom Edit Component Factory
Use custom search fields in edit panels:
class TrackEditComponentFactory extends DefaultEditComponentFactory<Entity, EntitySearchField> {
@Override
protected EntitySearchField.SingleSelectionBuilder searchField(
ForeignKey foreignKey,
EntityDefinition entityDefinition,
EntitySearchModel searchModel) {
return super.searchField(foreignKey, entityDefinition, searchModel)
.selectorFactory(new TrackSelectorFactory());
}
}
6.7. Configuration Properties
Property | Default | Description |
---|---|---|
|
WAIT_CURSOR |
How to indicate ongoing searches (WAIT_CURSOR or PROGRESS_BAR) |
6.8. Best Practices
-
Provide Clear Search Columns: Configure the search model with appropriate searchable columns
-
Consider Performance: Use result limits in the search model for large datasets
-
Keyboard Support: Leverage the built-in keyboard shortcuts (INSERT for add, CTRL+INSERT for edit)
-
Custom Selectors: Create custom selectors for complex selection scenarios
7. Reporting with JasperReports
Codion uses a plugin oriented approach to report viewing and provides an implementation for JasperReports.
With the Codion JasperReports plugin you can either design your report based on an SQL query in which case you use the JRReport class, which facilitates the report being filled using the active database connection, or you can design your report around the JRDataSource implementation provided by the JasperReportsDataSource class, which is constructed around an iterator.
7.1. JDBC Reports
Using a report based on an SQL query is the simplest way of viewing a report using Codion, just add a method similar to the one below to a EntityTablePanel subclass. You can then create an action calling that method and put it in for example the table popup menu as described in the adding a print action section.
public class CustomerTablePanel extends EntityTablePanel {
public CustomerTablePanel(SwingEntityTableModel tableModel) {
super(tableModel);
// associate a custom Control with the PRINT control key,
// which calls the viewCustomerReport method in this class,
// enabled only when the selection is not empty
control(PRINT).set(Control.builder()
.command(this::viewCustomerReport)
.caption("Customer report")
.smallIcon(FrameworkIcons.instance().print())
.enabled(tableModel().selection().empty().not())
.build());
}
private void viewCustomerReport() {
List<Entity> selectedCustomers = tableModel().selection().items().get();
if (selectedCustomers.isEmpty()) {
return;
}
Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
Map<String, Object> reportParameters = new HashMap<>();
reportParameters.put("CUSTOMER_IDS", customerIds);
JasperPrint customerReport = tableModel().connection()
.report(Customer.REPORT, reportParameters);
Dialogs.builder()
.component(new JRViewer(customerReport))
.owner(this)
.modal(false)
.title("Customer Report")
.size(new Dimension(800, 600))
.show();
}
}
7.2. JRDataSource Reports
The JRDataSource implementation provided by the JasperReportsDataSource simply iterates through the iterator received via the constructor and retrieves the field values from the underlying entities. The easiest way to make this work is to design the report using field names that correspond to the attribute names, so using the Store domain example from above the fields in a report showing the available items would have to be named 'name', 'active', 'category_code' etc.
EntityConnection connection = connectionProvider.connection();
EntityDefinition customerDefinition =
connection.entities().definition(Customer.TYPE);
Iterator<Entity> customerIterator =
connection.select(all(Customer.TYPE)).iterator();
JasperReportsDataSource<Entity> dataSource =
new JasperReportsDataSource<>(customerIterator,
(entity, reportField) ->
entity.get(customerDefinition.attributes().getOrThrow(reportField.getName())));
JRReport customerReport = fileReport("reports/customer.jasper");
JasperPrint jasperPrint = JasperReports.fillReport(customerReport, dataSource);