Validation Code & Cascading Lookups

First: You guys have crafted a remarkable develpment tool. I tip my hat . I’ve done some lightswitch programming and tried ExpressApp Framework and Cuba platform is better in almost every aspect I can think of.

That said, I have a couple of questions.

  1. Where do I put the validation code ( to be more specific, complex validations, like making sure the purchase quantity in an order is less than the available stock). And how do I notify the user that there is an error ( I am thinking in the equivalent of c# errorprovider ).
  2. How can I make cascading lookups ( eg : country > state > city ), so that once the country is selected , the lookup only shows states of that country.

Thanks and greetings from Mexico City

1 Like

Thank you for the encouraging feedback and for the great questions!

Regarding validation, I think, the closest equivalent of the C# ErrorProvider is our Validator interface. It allows you to check conditions on the UI tier before your screen commits data to the middleware.

On the middle tier, you can put some validation logic to an entity listener, particularly an implementation of BeforeInsertEntityListener and BeforeUpdateEntityListener. If you throw an exception in the entity listener, a current transaction will be rolled back. Then you can register an Exception Handler for your exception type to show some information to the user in this case.

We are thinking about implementing a more centralized and declarative way of validation, perhaps something like JavaEE Bean Validation. But the mentioned above mechanisms will remain because they give flexibility for complex cases.

Regarding cascading lookups. You can easily implement them using dependencies between datasources: see the ds$ query parameter prefix.

Let’s suppose that you have Country, City and Address entities. Address has links to Country and City, and the list of cities should be filtered by previously selected Country.

Below is the XML descriptor of the address editor:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        caption="msg://editCaption"
        class="com.company.cascadedlookups.gui.address.AddressEdit"
        datasource="addressDs" focusComponent="fieldGroup"
        messagesPack="com.company.cascadedlookups.gui.address">
    <dsContext>
        <datasource id="addressDs"
                    class="com.company.cascadedlookups.entity.Address"
                    view="address-browse-view"/>
        <collectionDatasource id="countriesDs"
                              class="com.company.cascadedlookups.entity.Country"
                              view="_minimal">
            <query>
                <![CDATA[select e from demo$Country e]]>
            </query>
        </collectionDatasource>
        <collectionDatasource id="citiesDs"
                              class="com.company.cascadedlookups.entity.City"
                              view="_minimal">
            <query>
                <![CDATA[select e from demo$City e
where e.country.id = :ds$countriesDs]]>
            </query>
        </collectionDatasource>
    </dsContext>
    <layout expand="windowActions" spacing="true">
        <fieldGroup id="fieldGroup" datasource="addressDs">
            <column width="250px">
                <field id="country" optionsDatasource="countriesDs"/>
                <field id="city" optionsDatasource="citiesDs"/>
                <field id="addressLine"/>
            </column>
        </fieldGroup>
        <frame id="windowActions" screen="editWindowActions"/>
    </layout>
</window>

Here you can see two collectionDatasources which serve as optionDatasources for fields in the field group. And citiesDs has a condition in the query where e.country.id = :ds$countriesDs which makes citiesDs refresh every time a new item is selected in countriesDs.

Now when the user selects a country, the list of cities is filtered. But if the user selects a country, then a city, and then returns to the country field and changes it, the city field will contain the old value which is wrong. To fix this, add the following code to the screen controller:


public class AddressEdit extends AbstractEditor<Address> {
    @Inject
    protected Datasource<Address> addressDs;

    @Override
    public void init(Map<String, Object> params) {
        addressDs.addItemPropertyChangeListener(e -> {
            if (e.getProperty().equals("country")) {
                addressDs.getItem().setCity(null);
            }
        });
    }
}

Here we add a listener to the main datasource of the screen, which clears the value of City attribute when Country changes.

The demo project is attached, you can open it in Studio and run the app.

cascaded-lookups.zip (30.5K)

1 Like