Calling refresh method for TreeDataGrid seems to be "reseting" removals in the data context

I have the following composition in my application:

image

Here, a document version (DocumentVersion) is composed of many document items (DocItem). There are more relations that are omitted for clarity here.
On the UI side, I have an editor for DocumentVersion hosting a fragment which is a browser for it´s items, as shown below:

image

The fragment is implemented as an ancestor browser fragment class and a more specialized document item browser which inherits from the previous and is parameterized by the class that is being browsed. The ancestor contains a generic TreeDataGrid component which is then complemented by the appropriate datasource on the descendant class. The data source loader is on the descendant class and it just returns the List contained within the DocumentVersion being edited by the host screen.

CRUD operations with the items are done by actions inside the TreeDataGrid associated with the buttons on its header.
The removal of items is implemented initially by the ancestor browser fragment, which after doing some initial checking, calls the overridden descendant fragment method to do the removal. If the removal is successfully done by the descendant fragment, then the ancestor fragment calls the “refresh” method of the TreeDataGrid to update the UI and the removed item disappears. The descendant fragment performs the removal by calling dataContext.remove(docItem), as show in code below:

   @Override
    protected boolean removeItem(DocItem item) {
        // Removes a document item from the document version being edited
        List<DocItem>dois = this.getItems(false);
        if (dois.contains(item)) {
            this.dataContext.remove(item);
            return true;
        }
        return false;
    }

The item removal handler on the ancestor fragment code is shown below (please notice the call to MainTree.refresh after item removal)

    @Subscribe("mainTree.remove")
    public void onMainTreeRemove(Action.ActionPerformedEvent event) {
        // Handle the "remove' command for a document item
        E item = this.getCurrentlySelectedItem();
        if (item == null){
            return;
        }
        dialogs.createOptionDialog()
                .withCaption(messages.getMessage("com.calidus.pdcs.screen.generic/browserMultiEditorFragment.confirmRemove.caption"))
                .withMessage(messages.getMessage("com.calidus.pdcs.screen.generic/browserMultiEditorFragment.remove.message"))
                .withActions(
                        new DialogAction(DialogAction.Type.YES)
                                .withHandler(e -> this.handleRemoveItem(item)), // execute action
                        new DialogAction(DialogAction.Type.NO)
                )
                .show();
    }
    protected void handleRemoveItem(E item){
        // Handle the removal of an item
        List<E>items = this.getItems(false); // Get items from doc version being edited on host screen
        // First, check if there are sub-items
        .
        .
        .
        .

        // Execute item removal
        final E parent = item.getParent();
        if (this.removeItem(item)){
            .
            .
            .
            // Refresh the document tree
            refresh();
        }
    }

The refresh method is on the ancestor fragment class:

    @Named("mainTree.refresh")
    protected RefreshAction refresh;
    .
    .
    .
    public void refresh(){
        // Refresh the tree of document items
        this.refresh.execute();
    }

Problem: Finally, the problem is that when I call the refresh method for the TreeDataGrid, the item removals inside the dataContext in the host editor screen appear to be reset, as shown below in the “before commit” event handler for the host screen:
image

As a result of that, the item is not deleted from the data store. If I don´t call the refresh method on the TreeDataGrid, the UI is not updated but the data context stays coherent and the item is removed from data store.

Does the refresh method interact with the data context somehow? Any ideas on that?

The xml for host screen and ancestor and descendant fragments are below:

Host screen:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://docVersionEdit.caption"
        focusComponent="form">
    <data>
        <instance id="docVersionDc"
                  class="com.calidus.pdcs.entity.documents.DocVersion">
            <fetchPlan extends="_base">
                <property name="dovDoc" fetchPlan="_base"/>
                <property name="dovDois" fetchPlan="_base">
                    <property name="doiDti" fetchPlan="_base"/>
                    <property name="doiTkd" fetchPlan="_base"/>
                </property>
            </fetchPlan>
            <loader id="docVersionDl"/>
        </instance>
    </data>
    <facets>
        <dataLoadCoordinator auto="true"/>
        <screenSettings id="settingsFacet" auto="true"/>
    </facets>
    <actions>
        <action id="windowCommitAndClose" caption="msg:///actions.Ok"
                icon="EDITOR_OK"
                primary="true"
                shortcut="${COMMIT_SHORTCUT}"/>
        <action id="windowClose"
                caption="msg:///actions.Close"
                icon="EDITOR_CANCEL"/>
    </actions>
    <dialogMode height="600"
                width="800"/>
    <layout spacing="true" expand="gbDoiBrowserEditor">
        <groupBox id="gbDovDetails" outerMargin="false" width="100%">
            <form id="form" dataContainer="docVersionDc" width="100%" stylename="small">
                <column width="100%">
                    <textField id="docCodeField" property="dovDoc.docCode" editable="false"/>
                    <textField id="dovIdField" property="dovId" editable="false"/>
                    <dateField id="dovCreationDateField" property="dovCreationDate" editable="false"/>
                </column>
                <column width="100%">
                    <textField id="docTitleField" property="dovDoc.docTitle" editable="false"/>
                    <comboBox id="dovLcsField" property="dovLcs" editable="false"/>
                    <dateField id="dovLastChangeDateField" property="dovLastChangeDate" editable="false"/>
                </column>
            </form>
        </groupBox>
        <groupBox id="gbDoiBrowserEditor" outerMargin="false" width="100%" expand="doiBrowserEditor">
            <fragment id="doiBrowserEditor" screen="DocItemBrowserEditorFragment" width="100%" responsive="true"/>
        </groupBox>
        <hbox id="editActions" spacing="true" align="BOTTOM_RIGHT">
            <button id="closeBtn" action="windowClose" stylename="small"/>
            <button id="commitAndCloseBtn" action="windowCommitAndClose" stylename="small"/>
        </hbox>
    </layout>
</window>

Ancestor fragment:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<fragment xmlns="http://jmix.io/schema/ui/fragment">
<data>
</data>
<layout width="100%" spacing="false" expand="split">
    <split id="split"
           orientation="horizontal"
           reversePosition="true"
           width="100%"
           dockable="true"
           dockMode="LEFT">
        <treeDataGrid id="mainTree"
                      width="AUTO"
                      height="100%"
                      headerVisible="true"
                      responsive="true">
            <actions>
                <action id="refresh"
                        type="refresh"/>
                <action id="noCaptionRefresh"
                        icon="font-icon:REFRESH"/>
                <action id="promote"
                        icon="font-icon:TOGGLE_LEFT"/>
                <action id="demote"
                        icon="font-icon:TOGGLE_RIGHT"/>
                <action id="remove"
                        icon="font-icon:TRASH_O"/>
                <action id="moveUp"
                        icon="font-icon:CHEVRON_UP"/>
                <action id="moveDn"
                        icon="font-icon:CHEVRON_DOWN"/>
            </actions>
            <columns>
            </columns>
            <!--                    <simplePagination/>-->
            <buttonsPanel id="buttonsPanel"
                          alwaysVisible="true" spacing="false" stylename="small">
                <popupButton id="createBtn"
                             icon="font-icon:PLUS"
                             stylename="small">
                    <actions>
                    </actions>
                </popupButton>
                <!--                        <button id="refreshBtn" action="doiTree.refresh" stylename="small"/>-->
                <button id="removeBtn" action="mainTree.remove" stylename="small"/>
                <button id="moveUpBtn" action="mainTree.moveUp" stylename="small"/>
                <button id="moveDnBtn" action="mainTree.moveDn" stylename="small"/>
                <button id="refreshBtn" action="mainTree.noCaptionRefresh" stylename="small"/>
                <button id="promoteBtn" action="mainTree.promote" stylename="small"/>
                <button id="demoteBtn" action="mainTree.demote" stylename="small"/>
            </buttonsPanel>
        </treeDataGrid>
        <scrollBox id="editSb"
                   width="100%"
                   height="100%"
                   spacing="false"
                   responsive="true"/>
    </split>
</layout>
</fragment>


Descendant fragment:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<fragment xmlns="http://jmix.io/schema/ui/fragment"
          caption=""
          extends="com/calidus/pdcs/screen/browsermultieditorfragment/browser-multi-editor-fragment.xml">
    <data>
        <collection id="docItemsDc"
                    class="com.calidus.pdcs.entity.documents.DocItem">
            <fetchPlan extends="_base">
                <property name="doiPath"/>
                <property name="doiTitle"/>
                <property name="doiParent" fetchPlan="_base"/>
                <property name="doiDti" fetchPlan="_base"/>
                <property name="doiTkd" fetchPlan="_base"/>
            </fetchPlan>
            <loader id="docItemsDl"/>
        </collection>
    </data>
    <layout>
        <split id="split">
            <treeDataGrid id="mainTree"
                          dataContainer="docItemsDc"
                          hierarchyProperty="doiParent">
                <actions>
                    <action id="createDti"
                            caption="msg://com.calidus.pdcs.screen.docitem/docItemBrowserEditorFragment.createDti.caption"/>
                    <action id="createTkd"
                            caption="msg://com.calidus.pdcs.screen.docitem/docItemBrowserEditorFragment.createTkd.caption"/>
                    <action id="copyStdTkd"
                            caption="msg://com.calidus.pdcs.screen.docitem/docItemBrowserEditorFragment.copyStdTkd.caption"/>
                </actions>
                <columns>
                    <column id="doiPath" property="doiPath" sort="ASCENDING"/>
                    <column id="doiTitle" property="doiTitle" sortable="false"/>
                </columns>
                <buttonsPanel id="buttonsPanel">
                    <popupButton id="createBtn">
                        <actions>
                            <action id="mainTree.createDti"
                                    icon="font-icon:FILE_TEXT"/>
                            <action id="mainTree.copyStdTkd"
                                    icon="font-icon:CERTIFICATE"/>
                            <action id="mainTree.createTkd"
                                    icon="font-icon:STICKY_NOTE_O"/>
                        </actions>
                    </popupButton>
                </buttonsPanel>
            </treeDataGrid>
        </split>
    </layout>
</fragment>

image

Thanks in advance.

Hi,

RefreshAction triggers load() method of corresponding data loader, i.e. it reloads data from data base. As far as I get, dataContext.remove(item) is the only code, you’re using to “remove” an item, but in fact, you’re not removing it, see JavaDoc:

Removes the entity from the context and registers it as deleted. The entity will be removed from the data store upon subsequent call to commit().

In simple words, you’re showing your intention to remove an item but not commiting it.

In simple case, standard RemoveAction should be enough, since it either remove or exclude items depending on association type, plus it informs corresponding ListComponent (Table, DataGrid, etc.) that items set has changed and it must repaint itself.

If custom remove implementation is needed, then you can either use io.jmix.ui.RemoveOperation bean, as RemoveAction does, or correctly inform DataGrid that its item set has changed by invoking container.getMutableItems().remove() instead of reloading the whole data.

if you still need to reload data, then commit removal first and only then refresh.

Regards,
Gleb

Hi Gleb, thanks for your answer. Please see my comments below.

RefreshActiontriggersload()` method of corresponding data loader, i.e. it reloads data from data base.

Yes, I already realized that but in my case, the loader is delegated to a local routine which just returns a set of items that are linked to the entity being edited by the host screen (see below).

    @Install(to = "tasksDl", target = Target.DATA_LOADER)
    protected List<Task> tasksDlLoadDelegate(LoadContext<Task> loadContext) {
        // Load the tasks for an activity
        return this.getItems(true);
    }

So, it does not interact with the database.

As far as I get, dataContext.remove(item) is the only code, you’re using to “remove” an item, but in fact, you’re not removing it

I call dataContext.remove on the fragment screen and it “merges” the operation into the dataContext of the host screen. When the user clicks on the “Save” button on the later, the windowCommittAndClose action is executed, committing the context into the database.

In simple case, standard RemoveAction should be enough, since it either remove or exclude items depending on association type

In my case, I have to check if an item can be deleted and that is why I implemented a customized delete action.

plus it informs corresponding ListComponent (Table , DataGrid , etc.) that items set has changed and it must repaint itself.

This is good information that is beyond the contents of the Jmix documentation. As soon as I can, I will explore that to see if I can trigger the list repaint from my custom delete action.

If custom remove implementation is needed, then you can either use io.jmix.ui.RemoveOperation bean, as RemoveAction does, or correctly inform DataGrid that its item set has changed by invoking container.getMutableItems().remove() instead of reloading the whole data.

I understand that as “calling refresh is not the correct way to go”. Ok, I will try to understand better what the container.getMutableItems().remove() and hopefully I will try it.

I intend to play more with this issue and report here after I find a solution. Thanks.

1 Like

I´ve made some experiences here and in the end, I called DataContext.remove to remove the item but I still need to call container.getMutableItems.remove() to make the item disappear from the UI.
I was expecting the UI to be automatically updated when I delete one item through the data context but that is not what happens.
Anyway, it is working now.