Jmix version: 2.4.2
Jmix Studio plugin version: 2.4.2-243
IntelliJ IDEA 2024.3 (Community Edition)
Build #IC-243.21565.193, built on November 13, 2024
Runtime version: 21.0.5+8-b631.16 x86_64 (JCEF 122.1.9)
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
GC: G1 Young Generation, G1 Concurrent GC, G1 Old Generation
Kotlin: 243.21565.193-IJ
java 17.0.10 2024-01-16 LTS
Java™ SE Runtime Environment (build 17.0.10+11-LTS-240)
Java HotSpot™ 64-Bit Server VM (build 17.0.10+11-LTS-240, mixed mode, sharing)
Operating System: macOS 12.7.6 (21H1320)
Metal Rendering: ON
File System: Case-Sensitive Journaled HFS+ (APFS)
Browser: Safari - Version 17.6 (17618.3.11.11.7, 17618)
Database: PostgreSQL 13
Hello Jmix Team
For your information, I am not able to reproduce the normal Composition DetailView behaviour using dialogWindows.detail()
. I will illustrate this problem using the attached example project:
composition.zip (2.3 MB)
In my example there are two compositions; one composition is with entities ItService
and Alert
, and the other is with entities DataCenter
and Server
; an ItService
has zero or more Alerts
and a DataCenter
has zero or more Servers
.
Example 1 (OK) - Default out-of-the-box functionality
I have created a default ItServiceListView
and default ItServiceDetailView
for the ItService
entity, and a default AlertDetailView
for the Alert
entity. None of these views contain any custom code; they have not been modified after they were created by the Studio wizard.
If I create a new ItService
entity from the ItServiceListView
and then create one or more new Alert
entities from the ItServiceDetailView
, then…
- the newly created
Alert
entities are displayed in theItServiceDetailView
’sdataGrid
and… - the newly created
Alert
entities are not persisted in the database until I save theItService
entity using theItServiceDetailView
’s OK button.
This is the normal expected behaviour.
Example 2 (NOK) - Custom functionality to initialise UI components
My second composition requires custom view logic to initialise some view components, therefore, I am explicitly calling dialogWindows.detail()
from the dataGrid
’s event handler instead of using the default action logic in example 1 above.
I have tried the following variations with different logic and none of them work correctly; the main problem is already seen in variation 1 and it is found in all of the other variations:
Variation 1 - Using newEntity()
@Subscribe("serverDataGrid.create")
public void onServerDataGridCreate(final ActionPerformedEvent event) {
DialogWindow<ServerDetailView> dialogWindow = dialogWindows.detail(this, Server.class)
.withViewClass(ServerDetailView.class)
.newEntity()
.build(); // The new view’s onInit() is called here.
dialogWindow.getView().setDataCenterName(dataCenterDc.getItem().getName());
dialogWindow.getView().setServerMemorySizeOptions(null); // null for a new entity
dialogWindow.open();
}
If I use variation 1 above, everything is initialised correctly, but as soon as save the new Server
entity with the ServerDetailView
’s OK button and return to the DataCenterDetailView
, I receive the following error:
PSQLException: ERROR: null value in column “data_center_id” of relation “server” violates not-null constraint
Detail: Failing row contains (9459eaef-f08c-c1ed-2c80-ca37de3e5dae, srv01, 20, null, 40).
This error indicates that the system is trying to persist my new Server
entity in the database, but it cannot because the DataCenter
entity is new and has not been persisted yet. However, the system should not persist the new Server
at all; it should only persist this new Server
entity when I explicitly persist the DataCenter
entity with the OK button in the DataCenterDetailView
.
Why does the system try and persist this Server entity?
Variation 2 - Manually create a new Server entity and pass it with newEntity(newServer)
@Subscribe("serverDataGrid.create")
public void onServerDataGridCreate(final ActionPerformedEvent event) {
Server newServer = dataContext.create(Server.class);
newServer.setDataCenter(dataCenterDc.getItem());
DialogWindow<ServerDetailView> dialogWindow = dialogWindows.detail(this, Server.class)
.withViewClass(ServerDetailView.class)
.newEntity(newServer)
.build(); // The new view’s onInit() is called here.
dialogWindow.getView().setDataCenterName(dataCenterDc.getItem().getName());
dialogWindow.getView().setServerMemorySizeOptions(null); // null for a new entity
dialogWindow.open();
}
If I use variation 2 above, everything is initialised correctly, but when I save the new Server
entity with the ServerDetailView
’s OK button and return to the DataCenterDetailView
, then both the new Server
entity and also the DataCenter
entity are persisted in the database, even though I have not pressed the DataCenterDetailView
’s OK button. Furthermore, the new Server
entity is not displayed in the DataCenter
’s dataGrid
; therefore, it appears that nothing happened at all.
If I press OK in the DataCenterDetailView
, then I get a “may not be null” alert notification without any other information.
If I press Cancel and then “Don’t save” in the confirmation dialog, thereby closing the DataCenterDetailView
, then I see the newly created DataCenter
entity in the DataCenterListView
, confirming that saving the Server
caused the DataCenter
also to be saved.
Variation 3 - Use withAfterCloseListener with afterCloseEvent.getView().getEditedEntity() to update dataContext and dataGrid
@Subscribe("serverDataGrid.create")
public void onServerDataGridCreate(final ActionPerformedEvent event) {
Server newServer = dataContext.create(Server.class);
newServer.setDataCenter(dataCenterDc.getItem());
DialogWindow<ServerDetailView> dialogWindow = dialogWindows.detail(this, Server.class)
.withViewClass(ServerDetailView.class)
.newEntity(newServer)
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
Server server = afterCloseEvent.getView().getEditedEntity();
server = dataContext.merge(server);
serverDc.getMutableItems().add(server);
dataContext.merge(server.getDataCenter());
} else {
dataContext.remove(newServer);
}
})
.build(); // The new view’s onInit() is called here.
dialogWindow.getView().setDataCenterName(dataCenterDc.getItem().getName());
dialogWindow.getView().setServerMemorySizeOptions(null); // null for a new entity
dialogWindow.open();
}
If I use variation 3 above, everything is initialised correctly; when I save the new Server
entity with the ServerDetailView
’s OK button and return to the DataCenterDetailView
, then both the new Server
entity and also the DataCenter
entity are persisted in the database, even though I have not pressed the DataCenterDetailView
’s OK button (as with variation 2), and the DataCenter
’s dataGrid
is updated and displays the new Server
.
But when I press the DataCenterDetailView
’s OK button, I receive the following error because the DataCenter
was already persisted when the new Server
was saved:
Unique constraint violation occurred (PK_DATA_CENTER)
Variation 4 - Use withAfterCloseListener and reload the Server with dataManager.load() to update dataContext and dataGrid
@Subscribe("serverDataGrid.create")
public void onServerDataGridCreate(final ActionPerformedEvent event) {
Server newServer = dataContext.create(Server.class);
newServer.setDataCenter(dataCenterDc.getItem());
DialogWindow<ServerDetailView> dialogWindow = dialogWindows.detail(this, Server.class)
.withViewClass(ServerDetailView.class)
.newEntity(newServer)
.withAfterCloseListener(afterCloseEvent -> {
if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
Server server = dataManager.load(Server.class).id(afterCloseEvent.getView().getEditedEntity().getId()).one();
server = dataContext.merge(server);
serverDc.getMutableItems().add(server);
dataContext.merge(server.getDataCenter());
} else {
dataContext.remove(newServer);
}
})
.build(); // The new view’s onInit() is called here.
dialogWindow.getView().setDataCenterName(dataCenterDc.getItem().getName());
dialogWindow.getView().setServerMemorySizeOptions(null); // null for a new entity
dialogWindow.open();
}
If I use variation 4 above, everything is initialised correctly; when I save the new Server
entity with the ServerDetailView
’s OK button and return to the DataCenterDetailView
, then both the new Server
entity and also the DataCenter
entity are persisted in the database, even though I have not pressed the DataCenterDetailView
’s OK button (as with variations 2, 3), the DataCenter
’s dataGrid
is updated and displays the new Server
, and it is possible to press the DataCenterDetailView
’s OK button without receiving an alert notification. This is still not the expected Composition behaviour; saving the new Server
should not persist the new Server
and the new DataCenter
in the database.
Conclusion: None of these variations work; the normal Composition behaviour is not respected when using dialogWindows.detail()
is this manner. The new Server
entity should not be persisted to the database until the DataCenter
entity is explicitly persisted and the new DataCenter
entity should not be persisted with the new Server
, when the Server
is persisted.
My application is heavily dependent upon dialogWindows.detail()
, so that I can initialise multiple select
and other UI components.
What do I need to do to resolve these problems?
Please note, that I also need to use dialogWindows.detail()
when editing an already existing entity; therefore, the dataGrid
update problem needs to be solved for this case also.
Thank you in advance for your support and feedback.
Best regards
Chris