Filters for non-persistent entities

I’m running into a case where I have a datagrid on non-persistent entities and need filtering on them.

I found an older forum post on this topic where @knstvk mentions that one day there should be a decoupling between filters and jpql.

It’s a rather old post still based on datasources instead of datacollections. But I assume filters are still coupled to jpql queries on datacontainers? Is there a timeline on this?

For the moment the filtering will only be used in a one or 2 screens so I guess I will have to implement the filtering myself. Any recommendations on how to do this? Should I just be updating the collectioncontainer through setitems on every filter change? Should I extend or implement a new type of collectionContainer that supports filtering and emits events when filters change?

I use a datagrid due to the need for multiselection, not sure how the datagrid will react when filtering out selected items.

I can see more use cases popping up (integration with data from external system instead of local db).

Hi,

May I ask you how do you load your entities into the container? Do you use the data loader component?

No, I just use container.setItems methods.

I am not aware of any data loaders for non-persistent entities.
The documentation also doesn’t mention anything about that and only speaks about jpql.

CollectionLoader loads a collection of entities to CollectionContainer by a JPQL query. You can specify paging, sorting and other optional parameters.

Hi,

Unfortunately, the filter is still coupled to JPQL, so you’ll probably need to implement filtering by yourself. I can show you a workaround that might be useful, depending on your case.

Let’s assume you want to load non-persistent persons from a 3rd party datastore with name and email properties.

  1. I would suggest implementing a service with a method that fetches your data and has a parameter that can contain filter conditions.
  2. On the screen I’d recommend installing load delegate to your data loader and in this delegate, I’d invoke the service. Please note that you can get some information about screen load parameters from the LoadContext class (like query parameters) and filter component (like max result to load).

You can add custom properties to your filter:

        <filter id="filter"
                applyTo="personsTable"
                dataLoader="personsDl">
            <custom name="Name" caption="Name" paramClass="java.lang.String">
                {E}.name = ?
            </custom>
            <custom name="Email" caption="Email"  paramClass="java.lang.String">
                {E}.email = ?
            </custom>
        </filter>

Your service interface may look like this:

public interface PersonsService {
    List<Person> fetchPersons(Map<String, Object>  params);
}

In the PersonsServiceBean class you need to implement the logic of fetching data based on the params values.

Then in the browser screen you may have the empty data loader:

    <data readOnly="true">
        <collection id="personsDc"
                    class="com.company.filtering.entity.Person">
            <view extends="_local">
                <property name="name"/>
                <property name="email"/>
            </view>
            <loader id="personsDl"/>
        </collection>
    </data>

and in the controller you have the loader algorithm overridden:

    @Install(to = "personsDl", target = Target.DATA_LOADER)
    private List<Person> personsDlLoadDelegate(LoadContext<Person> loadContext) {
        Map<String, Object> params = new HashMap<>();
        params.putAll(loadContext.getQuery().getParameters());
        return personsService.fetchPersons(params);
    }

Now every time you update the filter, the data will be reloaded by the screen automatically. This approach makes the data loading process “seamless” for the screen, it does not care where entities are coming from. And you will have enough flexibility in terms of data loading - there is only one entry point for loading data: loader delegate.

The issue here is that filter parameter names are different from what you have defined in the XML screen markup. So, in the params map in the service, you can have param names like filter_Name8743 or filter_Email9173, because these names are defined in the runtime and redefined each time when the screen is opened.

Please have a look at the documentation on data loader and filter component, there are more examples of using data loaders in the screens that you may adapt to your case.

Also I’ve created a very basic prototype that can select persons by their names - the service contains static list of these. Hope this will help. filtering.zip (83.2 KB)

Just a reminder, it is a workaround. But using services and overriding data loader instead of direct adding items to the container is a good practice.

2 Likes

Thx, this is a very useful example.

1 Like