Add collection datasource listener to extended legacy screen controller

Hello CUBA team,

I have extended the User entity and edit screen in a CUBA project to add a few instance fields, which has been working as expected. One thing I now need to do is detect when a UserRole collection has changed within the extended legacy User edit screen so I can take some further action. The problem I’m running into is that the dataSource for the UserRole collection doesn’t seem to be available to my extended screen. This screen was extended some time ago, so maybe it wasn’t done correctly.

ext-user-edit.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        xmlns:ext="http://schemas.haulmont.com/cuba/window-ext.xsd"
        class="com.coral.registry.web.screens.user.ExtUserEditor"
        extends="com/haulmont/cuba/gui/app/security/user/edit/user-edit.xml"
        messagesPack="com.coral.registry.web.screens.user">
    <dsContext>
        <datasource id="userDs" view="extUser-view"/>
        <collectionDatasource id="organizationsDs" class="com.coral.registry.entity.Organization" view="_base">
            <query>
                <![CDATA[select e from registry_Organization e order by e.name]]>
            </query>
        </collectionDatasource>
    </dsContext>
    <layout>
        <groupBox>
            <grid>
                <columns>
                    <column id="fieldGroupLeftColumn"/>
                    <column id="fieldGroupRightColumn" flex="1"/>
                    <column id="fieldGroupExtColumn" flex="3"/>
                </columns>
                <rows>
                    <row>
                        <fieldGroup id="fieldGroupExt" datasource="userDs" width="AUTO">
                            <column width="250px">
                                <field id="requestedRole" property="requestedRole" editable="false"/>
                                <field id="phoneNumber" property="phoneNumber"/>
                                <field id="organization">
                                    <lookupPickerField id="organizationField" datasource="userDs"
                                                       property="organization" optionsDatasource="organizationsDs"
                                                       required="true"/>
                                </field>
                                <field id="reportedOrgField" property="reportedOrg" caption="Reported Org"
                                       editable="false"/>
                                <field id="addField" width="100%">
                                    <button id="addBtn" caption="Add Organization" width="AUTO"
                                            align="MIDDLE_RIGHT"/>
                                </field>
                            </column>
                        </fieldGroup>
                    </row>
                </rows>
            </grid>
        </groupBox>
    </layout>
</window>

ExtUserEditor.java

@Entity(name = "registry_ExtUser")
public class ExtUserEditor extends UserEditor {
    @Inject
    private ScreenBuilders screenBuilders;
    @Inject
    private OrganizationService organizationService;
    @Inject
    private Button addBtn;

    @Subscribe
    public void onBeforeShow(BeforeShowEvent event) {
        ExtUser editedEntity = (ExtUser) getEditedEntity();
        if (editedEntity.getOrganization() != null) {
            addBtn.setVisible(false);
        }
    }

    @Subscribe("organizationField")
    public void onOrganizationFieldValueChange(HasValue.ValueChangeEvent<Organization> event) {
        if (event.isUserOriginated()) {
            addBtn.setVisible(event.getValue() == null);
        }
    }

    @Subscribe("addBtn")
    public void onAddBtnClick(Button.ClickEvent event) {
        ExtUser editedEntity = (ExtUser) getEditedEntity();
        String reportedOrg = editedEntity.getReportedOrg();
        String email = editedEntity.getEmail();
        MapScreenOptions options = new MapScreenOptions(ParamsMap.of("emailDomain",
                email != null ? organizationService.getDomainFromEmail(email) : ""));

        screenBuilders.editor(Organization.class, this)
                .withScreenClass(OrganizationEdit.class)
                .newEntity()
                .withInitializer(organization -> {
                    if (reportedOrg != null) {
                        organization.setName(reportedOrg);
                    }
                })
                .withOptions(options)
                .withAfterCloseListener(organizationEditAfterScreenCloseEvent -> {
                    if (organizationEditAfterScreenCloseEvent.closedWith(StandardOutcome.COMMIT)) {
                        Organization organization = organizationEditAfterScreenCloseEvent.getSource().getEditedEntity();
                        ((ExtUser) getEditedEntity()).setOrganization(organization);
                    }
                })
                .build()
                .show();
    }
}

So even though the screen (descriptor and controller) are extended and function as expected, I can’t seem to find a way to inject the DataSource from the parent screen.

Do I maybe need to recreate the descriptor/controller in full and replace the “parent” screen instead of extending it? Or is there another way to detect changes directly in the UserRole collection?

Thanks!

Adam

One thing I noticed is that the declared dataSource in my extended descriptor file didn’t have a class specified. So I changed that to:

<datasource id="userDs" class="com.coral.registry.entity.ExtUser" view="extUser-view"/>

Prior to that, when I tried rewriting the dataSource to include its inner collections, the IDE didn’t recognize the properties of the inner collections. After I added it, they were recognized and flagged as redundant, so it seems that adding the class to the dataSource was needed, though I’m not yet sure how the screen was working without the class specified.

Now that that is in place, the parent User screen’s rolesDs dataSource still isn’t available to the extended controller, so I’m attempting something like this:

    @Subscribe
    public void onInit(InitEvent event) {
        GroupDatasource rolesDs = (GroupDatasource) super.dsContext.get("rolesDs");
        rolesDs.addItemChangeListener(e -> {
            // checking contents of roles group
        });
    }

Is this on the right track? Or is this not going to properly do what I need to detect a change directly in the userRoles collection?

Also, when I add a postCommit handler from the ‘generate handler’ options, it immediately fails in runtime when I try to access the screen, saying something like ‘unable to find subscribe target’. This is what I added, with no additional code:

    @Subscribe(target = Target.DATA_CONTEXT)
    public void onPostCommit(DataContext.PostCommitEvent event) {

    }

Does this target value need to reference the super class somehow?

Okay okay okay…

So the rolesDs isn’t injectable because it’s already been injected into the parent screen controller! So I can reference it directly. Well shoot…that problem is solved. But, I still need to figure out the post commit handler problem.

So I guess another applicable question to this scenario is how to work with DataContext. In this legacy controller, one thing I’m doing is creating a convenience method to automatically remove all UserRoles and add a different one based on a button click. My current, non-working, method is below:

    @Subscribe("approveRequestedRoleBtn")
    public void onApproveRequestedRoleBtnClick(Button.ClickEvent event) {
        //Remove all existing from dataContext
        ExtUser user = (ExtUser) getEditedEntity();
        List<UserRole> currentUserRoles = new ArrayList<>();
        currentUserRoles.addAll(user.getUserRoles());
        getEditedEntity().getUserRoles().removeAll(currentUserRoles);
        for (UserRole currentUserRole : currentUserRoles) {
            rolesDs.removeItem(currentUserRole);
        }
        //Add requested role to dataContext
        UserRole requestedUserRole = metadata.create(UserRole.class);
        requestedUserRole.setUser(getEditedEntity());
        requestedUserRole.setRoleName(user.getRequestedRole().getId());

        UserRole merged = dataContext.merge(requestedUserRole);
        rolesDs.addItem(merged);
        user.getUserRoles().add(merged);
    }

This seems to remove the UserRoles, but doesn’t add the new UserRole.

Thanks in advance,
Adam

Hi,

Could you please clarify which CUBA version you are using?

Gleb

This project uses CUBA Platform 7.2.19

It doesn’t look like my message flagged you, so I’m messaging again.

In legacy screens you need to work with DsContext instead of DataContext, e.g.:

@Override
public void init(Map<String, Object> params) {
    getDsContext().addBeforeCommitListener(context -> {
        if (customer != null)
            context.getCommitInstances().add(customer);
    }
}