How to migrate a custom data source to v7?

Warning – lots of code here… :wink:

I am trying to migrate the following code to v7. The code uses our business rules (in a service) to identify customers who are bad debt risks and display them to the user. The user can then Accept or Reject the order.

public class BadDebtDatasource extends CustomCollectionDatasource<BadDebt, Long> {

    private final BadDebtorService badDebtorService = AppBeans.get(BadDebtorService.NAME);

    @Override
    protected Collection<BadDebt> getEntities(Map params) {
        String lname = (String) params.get("lname");
        String postalCode = (String) params.get("postalCode");

        if (lname.isEmpty() || postalCode.isEmpty()) {
            return new HashSet<>();
        }
        else {
            return badDebtorService.check_bad_debt(lname, postalCode);
        }
    }
}

What is the v7 equivalent of a CustomCollectionDatasource?

I use it like this - which also needs to be translated to v7. An AbstractWindow becomes a Screen? How do parameters get passed, as I need to pass some to the service? How do I pass the resulting pressed button back to the calling code?

public class Baddebtlist extends AbstractWindow {

    @Inject
    private BadDebtDatasource badDebtDs;

    @Override
    public void init(Map<String,Object> params)
    {
        super.init(params);
        getDialogOptions().setModal(true);
        WindowParams.DISABLE_AUTO_REFRESH.set(params, true);
        badDebtDs.refresh(params);
    }

    public void onAccept(Component source) {
        close("onAccept");
    }

    public void onReject(Component source) {
        close("onReject");
    }
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd" caption="Possible Bad Debt "
        class="com.paslists.rade.web.badDebtList.Baddebtlist" focusComponent="btnAccept"
        messagesPack="com.paslists.rade.web.badDebtList">
    <dsContext>
        <collectionDatasource id="badDebtDs" allowCommit="false" class="com.paslists.rade.entity.BadDebt"
                              datasourceClass="com.paslists.rade.gui.BadDebtDatasource" refreshMode="NEVER"/>
    </dsContext>
    <dialogMode forceDialog="true" height="600" resizable="true" width="850"/>
    <layout>
        <vbox id="vboxBadDebt" expand="tblBadDebt" height="500px" spacing="true">
            <buttonsPanel id="tblBadDebtButtonsPanel">
                <button id="btnAccept" invoke="onAccept" caption="Accept Buyer"/>
                <button id="btnReject" invoke="onReject" caption="Reject Buyer"/>
            </buttonsPanel>
            <table id="tblBadDebt" showSelection="false" width="850px">
                <columns>
                    <column id="lname" caption="Last" width="100"/>
                    <column id="fname" caption="First" width="100"/>
                    <column id="address1" caption="Address 1" width="150"/>
                    <column id="address2" caption="Address 2" width="150"/>
                    <column id="city" caption="City" width="100"/>
                    <column id="province" caption="State/Province" width="80"/>
                    <column id="postalCode" caption="Postal Code" width="150"/>
                    <column id="countryCode" caption="Country Code" width="50"/>
                </columns>
                <rows datasource="badDebtDs"/>
            </table>
        </vbox>
    </layout>
</window>

This all gets used like this:

  1. see if proposed customer is a bad debt:
    private boolean check_bad_debt(Mailfile selected) {
        badDebtDs.refresh(ParamsMap.of("lname", selected.getLname(), "postalCode", selected.getZip()));
        return badDebtDs.getItemIds().size() > 0;
    }
  1. If it is, display the bad debt screen and get the result:
    private void check_bad_debt_and_create_updtrans(Updtrans updtrans, Mailfile selected) {
        if (check_bad_debt(selected)) {
            openWindow("rade$badDebtList",
                    WindowManager.OpenType.DIALOG,
                    ParamsMap.of("lname", selected.getLname(), "postalCode", selected.getZip())
            ).addCloseListener(actionId -> {
                if (actionId.equals("onAccept")) {
                    createUpdtrans(updtrans, selected);
                }
                return;
            });
        } else {
            createUpdtrans(updtrans, selected);
        }
    }

Hi!

Firstly, about passing window parameters you can read here.
To get result button you can add AfterCloseListener for created screen.

       screenBuilders.editor(badDebtsTable)
                .show()
                .addAfterCloseListener(afterCloseEvent -> {
                    CloseAction closeAction = afterCloseEvent.getCloseAction();
                    if (closeAction instanceof StandardCloseAction && ((StandardCloseAction) closeAction).getActionId().equals(Window.COMMIT_ACTION_ID)) {
//                        do something
                    }
        });

In CUBA 7, loading data logic is moved from datasources (which now are called data containers), to separate objects data loaders. You can install some custom logic to loader with @Install annotation.

<!--Part of screen descriptor-->
 <data readOnly="true">
    <collection id="badDebtsDc"
                    class="com.company.customcollectionv7.entity.BadDebt"
                    view="_local">
        <loader id="badDebtsDl"/> <!--Loader definition-->
    </collection>
</data>
// part of screen controller
@Install(to = "badDebtsDl", target = Target.DATA_LOADER)
private List<BadDebt> badDebtsDlLoadDelegate(LoadContext<BadDebt> loadContext) {
    return someService.loadBadDebts(someParams);
}

CUBA Studio can generate correct empty method for you. Just press Alt+Insert (Cmd+N on macOS), select Install Delegate and choose the installation point.

Thanks for the reply… However, I am not quite clear on one issue. My code specifically returns its own strings of “onAccept” and “onReject”, as that was the way to return which button was pushed in v6.

Can StandardCloseAction return different IDs? How do I assign them?

   ((StandardCloseAction) closeAction)getActionId().equals(Window.COMMIT_ACTION_ID))

How will this identify “onAccept” vs “onReject”?

Hi Eric,

We’ve just added the following section to documentation on the new screens API: Executing code after close and returning values.
I hope it answers your question. If something is not clear - feel free to ask here.

Thank you! It is very clear. However, I did write something before you added this documentation. Would it also work?

  1. I created routines to invoke in my screen controller:
    public void onAccept(Component source) {
        close(new StandardCloseAction("onAccept"));
    }

    public void onReject(Component source) {
        close(new StandardCloseAction("onReject"));
    }
  1. I invoke them from my screen via button Actions:
           <buttonsPanel id="tblBadDebtButtonsPanel">
                <button id="btnAccept" invoke="onAccept" caption="Accept Buyer"/>
                <button id="btnReject" invoke="onReject" caption="Reject Buyer"/>
            </buttonsPanel>
  1. I capture it like this:
            BaddebtlistBrowse baddebtlistBrowse = screenBuilders
                    .screen(this)
                    .withScreenClass(BaddebtlistBrowse.class)
                    .withLaunchMode(OpenMode.DIALOG)
                    .build();
            baddebtlistBrowse.addAfterCloseListener(afterCloseEvent -> {
                        if (afterCloseEvent.getCloseAction().equals("onAccept")) {
                            createUpdtrans(updtrans, selected);
                        }
                   });

Absolutely. It’s basically the same as the last example from the doc.
But I would recommend using @Subscribe instead of XML invoke where possible.

Thanks!