Table item is not updated after editing the target entity of a Many-to-Many relationship

Jmix version: 1.2.3
Jmix Studio plugin version: 1.2.3-213
IntelliJ IDEA 2022.1.1 (Community Edition)
Build #IC-221.5591.52, built on May 10, 2022
Runtime version: 11.0.14.1+1-b2043.45 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Operating System: macOS 12.3.1 (21E258)
File System: Case-Sensitive Journaled HFS+ (APFS)
Datebase: PostgreSQL 13

Hello Everyone

For your information, I had the following problem in CUBA and it still exists after migrating to Jmix.

I extended the Jmix Quick Start example https://github.com/jmix-framework/jmix-quickstart with Member and Tasks entities to display this behavior; here is my example:
jmix-pm-table_item_does_not_update.zip (100.0 KB)

Each Member can have one or more of the same tasks; therefore a Many-to-Many model is required and my intermediate Tasks DB-table stores the references to the Member and Task entities. (Please note, that I did not add an association between Project and Member, so I could keep my example as simple as possible.)

I can add a Task to the tasksTable in the Member_.edit screen or remove it and the tasksTable items are updated. However, if I edit a tasksTable’s Task and save it, the changes are not shown; the edited item in the tasksTable is not updated/refreshed.

If I add an AfterCloseListener (shown below but commented out in my attached example code), then the table entry is updated as expected but all other changes, e.g. if the Member’s name attribute was modified before the Task was edited, are lost; the memberDl.load() call discards all modifications made to the memberDc item’s attributes. Therefore, this memberDl.load() function cannot be used.

@Subscribe("tasksTable.edit")
public void onTasksTableEdit(Action.ActionPerformedEvent event) {
    screenBuilders.editor(Task.class, this)
            .withOpenMode(OpenMode.DIALOG)
            .editEntity(tasksDc.getItem().getTask())
            .withScreenClass(TaskEdit.class)
            .withAfterCloseListener(afterCloseEvent -> {
                CloseAction closeAction = afterCloseEvent.getCloseAction();
                if (!((StandardCloseAction) closeAction).getActionId().equals("mainMenu")) {
                    memberDl.load();     // refresh table BUT THIS CAUSES ALL UNSAVED memberDc CHANGES TO BE LOST
                }
            })
            .build()
            .show();

}

How can I refresh only the table items in this situation? In my application I have several screens with multiple tables in this same configuration.

Can you please suggest or provide a solution. Thanks in advance for your feedback.

Best regards
Chris

Hi Chris,

You should set the edited Task instance back to the Tasks which is shown in the table:

@Subscribe("tasksTable.edit")
public void onTasksTableEdit(Action.ActionPerformedEvent event) {
    screenBuilders.editor(Task.class, this)
            .withOpenMode(OpenMode.DIALOG)
            .editEntity(tasksDc.getItem().getTask())
            .withScreenClass(TaskEdit.class)
            .withAfterCloseListener(afterCloseEvent -> {
                CloseAction closeAction = afterCloseEvent.getCloseAction();
                if (!((StandardCloseAction) closeAction).getActionId().equals("mainMenu")) {
                    tasksDc.getItem().setTask(afterCloseEvent.getSource().getEditedEntity());
                }
            })
            .build()
            .show();
}

Also, if you want the changes in Task to be saved only when user clicks OK in the Member editor, pass the DataContext to the invoked editor (then it will save data to the parent data context instead of the database):

@Subscribe("tasksTable.edit")
public void onTasksTableEdit(Action.ActionPerformedEvent event) {
    screenBuilders.editor(Task.class, this)
            .withParentDataContext(dataContext)
            .withOpenMode(OpenMode.DIALOG)
            .editEntity(tasksDc.getItem().getTask())
            .withScreenClass(TaskEdit.class)
            .withAfterCloseListener(afterCloseEvent -> {
                CloseAction closeAction = afterCloseEvent.getCloseAction();
                if (!((StandardCloseAction) closeAction).getActionId().equals("mainMenu")) {
                    tasksDc.getItem().setTask(afterCloseEvent.getSource().getEditedEntity());
                }
            })
            .build()
            .show();
}

@krivopustov

Hi Konstantin

Thank you very much for the resolution; it works perfectly.

I was so convinced that the framework should automatically update the table, or that I had to somehow trigger an auto-update, that I did not even consider manually updating the data collection. My nearsightedness.

I would recommend to add your resolution information to the documentation because I am not sure if everyone is immediately aware that the load() will discard unsaved changes. For example, here is the same situation that I described but the load() method was the apparent solution: Refresh data in CUBA screen

I also appreciate the tip regarding the parent data context; I had not seen it in the documentation and I believe that I may have use for it.

Thanks again.

Best regards
Chris