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)