4.5.3.3. Data Source Listeners

With datasource listeners it's possible to receive notifications about changes in data source states and entity instances located inside them.

To register listeners, the Datasource.addListener(), Datasource.removeListener() methods are used. Below is an example of registering a listener in a screen controller:

@Inject
private Datasource<Customer> customerDs;

...
public void init(Map<String, Object> params) {
    ...
    customerDs.addListener(new DatasourceListener<Customer>() {
        // listener methods implementation
    });
}

There are two listener interfaces of datasources: DatasourceListener and CollectionDatasourceListener. The first can be used for registration in any of datasources, the second – only in those implementing CollectionDatasource. Typically, in practice, it's not necessary to receive all notifications from a listener. That's why it is convenient to use class-adapters, DsListenerAdapter and CollectionDsListenerAdapter, instead of implementation of listener interfaces, which contain empty implementations of all methods of corresponding interfaces.

DatasourceListener methods are provided below:

  • valueChanged() – declaration of this method is inherited from the base interface, ValueListener. This listener method is invoked if an attribute value of some entity that is currently located in the source has changed. The modified instance itself, the name of changed attribute, old and new values are passed into the method.

    valueChanged() notification can be used to respond to user changes in an entity from the UI, i.e., editing input fields. In the example below, a hypothetical method, updateSettings(), will be invoked when the value of the active attribute is changed, and a new attribute value will be passed into this method:

    @Inject
    private Datasource<Customer> customerDs;
    
    public void init(Map<String, Object> params) {
        ...
        customerDs.addListener(new DsListenerAdapter<Customer>() {
            @Override
            public void valueChanged(Customer source, String property, Object prevValue, Object value) {
                if ("active".equals(property)) {
                    boolean active = BooleanUtils.isTrue((Boolean) value); // converting null to false
                    updateSettings(active);
                }
            }
        });
    }

  • itemChanged() – is invoked when a selected instance returned by the getItem() method is changed.

    For Datasource, it happens when another instance (or null) is set with setItem() method.

    For CollectionDatasource, this notification is invoked when a selected element is changed in a linked visual component. For example, it may be a selected table row, tree element or item in a drop-down list.

    Below is an example of the itemChanged()notification to control the state of an action of the table:
    @Inject
    protected CollectionDatasource<Customer, UUID> customersDs;
    
    @Named("customersTable.remove")
    protected RemoveAction removeAction;
    
    public void init(Map<String, Object> params) {
        ...
        customersDs.addListener(new DsListenerAdapter<Customer>() {
            @Override
                public void itemChanged(Datasource<Customer> ds, Customer prevItem, Customer item) {
                removeAction.setEnabled(canCustomerBeDeleted(item));
            }
        });
    }
  • stateChanged() – is invoked when a state of the data source is changed. The data source can be in one of three states corresponding to the Datasource.State enumeration:

    • NOT_INITIALIZED – source has just been created.

    • INVALID – the whole DsContext, which this source is related to, is created.

    • VALID – data source is in working state: Datasource contains an entity instance or null, CollectionDatasource – collection of instances or an empty collection.

    Receiving a notification about changes in source state may be important for complex editors, which consist of several frames where it is difficult to trace the moment of putting an edited entity into the source. In this case, stateChanged() notification for the delayed initialization of certain screen elements can be used:

    @Inject
    protected CollectionPropertyDatasourceImpl<CategoryAttribute, UUID> categoryAttrsDs;
    
    categoryAttrsDs.addListener(new DsListenerAdapter<CategoryAttribute>() {
        @Override
        public void stateChanged(Datasource ds, Datasource.State prevState, Datasource.State state) {
            if (state != Datasource.State.VALID) return;
    
            initDataTypeColumn();
            initDefaultValueColumn();
        }
    });

The CollectionDatasourceListener interface adds one more method:

  • collectionChanged() – is invoked when a entity collection, which is stored in the data source, is changed. One of the following type of changes is passed into the method: REFRESH, CLEAR, ADD, REMOVE, UPDATE.

    Below is an example of a listener that invokes the recalculation of a journey cost in case of the address of a stop (the Stop entity) or the number of stops is changed:

    protected class StopDsListener extends CollectionDsListenerAdapter<Stop> {
        @Override
        public void valueChanged(Stop source, String property, Object prevValue, Object value) {
            // existing stop address changed
            if ("address".equals(property)) {
                fireRouteChanged();
            }
        }
    
        @Override
        public void collectionChanged(CollectionDatasource ds, Operation operation) {
            // stop was added or removed
            fireRouteChanged();
        }
    
        private void fireRouteChanged() {
            // journey route has changed, need to recalculate price, journey time, pickup time delay etc.
        }
    }