Loading...
Important Notice - Forums is archived

To simplify things and help our users to be more productive, we have archived the current forum and focus our efforts on helping developers on Stack Overflow. You can post new questions on Stack Overflow or join our Discord channel.

Product icon
TUTORIAL

Vaadin lets you build secure, UX-first PWAs entirely in Java.
Free ebook & tutorial.

Vaadin 8 Grid - multiple filter configuration?

Steve Demy
5 years ago Jan 03, 2017 7:05am

I'm planning to replace 40 SQLContainer-backed Grids with the new data model of Vaadin 8.  A typical grid might look like this, with multiple user input filters of various types (normally String and java.sql.Timestamp).

Planning the service interface and implementing the backing SQL query is clear enough, but I can't figure out how to configure the BackEndDataProvider and or ListDataProvider to handle the multiple user filter inputs.  DataProvider's query.getFilter() method seems to expect a single string filter.  While ListDataProvider appears to provide for many, I'm not clear on how to cascade the filters nor on how to write the corresponding DataProvider queries.

An example outlining how multiple filters would be set would clear my confusion.

Alexander Ley
5 years ago Jan 04, 2017 9:41am
Marius Klein
5 years ago Jan 04, 2017 1:36pm
Steve Demy
5 years ago Jan 06, 2017 7:38am
Marco Collovati
5 years ago Jan 06, 2017 4:28pm
Steve Demy
5 years ago Jan 07, 2017 12:40am
Marco Collovati
5 years ago Jan 07, 2017 5:16pm
Steve Demy
5 years ago Jan 23, 2017 8:01am
Leif Åstrand
5 years ago Jan 23, 2017 1:32pm

Sorry for not discovering this discussion until now. I guess you could have been spared from some frustration if someone with insights into how things are supposed to work would have provided input earler on.

We quickly realized that there were some limitations in the filtering approach that was part of beta1, especially for the use case discussed here with multiple filters from different Grid columns. It did take us some time to figure out exactly what we wanted to do about that and also getting it done. I believe that the last functional change related to this was merged some minutes ago, but I'll still also update the documentation to describe how to use the new functionality.

When using a back end data provider, there are two general approaches. One option is to create you own custom subclass with separate setXyzFilter methods that store the filter in state variables that are read by the fetch and size methods without looking at Query.getFilter() at all. Just remember to also do refreshAll() in each setter so that Grid can know that it should fetch items again. The other option is to create a DTO class that contains all the different values to filter by and making your data provider accept that DTO as the query filter. You can then use BackEndDataProvider.withConfigurableFilter() to get a wrapper instance with a setFilter method that accepts a DTO, handles all refreshing automatically and passes the DTO the query for the wrapped data provider to use.

If using a ListDataProvider, the most straightforward approach is most likely to do one addFilter for each filter component with a predicate that dynamically checks the value from the component. Whenever a filter component changes, you can then do dataProvider.refreshAll() to make it rerun all filters. You could also use some combination of addFilter, setFiler and/or clearFilters() whenever a filter input component changes, but you'd then need to do some additional bookeeping to also preserve filters from the other components. As a last option, you can also use a similar DTO as suggested for the back end case and then use convertFilter (which should probably be renamed to withConvertedFilter to be consistent with other similar methods) to translate that DTO into a single predicate. This options requries some additional boilerplate compared to the other alternatives, but it has the benefit of allowing you do things in mostly the same way regardless of whether you're using in-memory or lazy-loaded data.

Sebastian Filke
5 years ago Mar 30, 2017 7:33am

Thank you very much Leif for the explanation, but i really don't get the filtering working with vaadin 8. There are one or two concepts i don't understand yet and i try to explain why.

With Vaadin 7 i was able to generate a "generic" way to add and remove filter for the containers for every column in the grid.
 

private void addGridFilters() {
        final HeaderRow filterRow = gridFacility.appendHeaderRow();
        for (final Object propertyId : gridFacility.getContainerDataSource().getContainerPropertyIds()) {
            final HeaderCell headerCell = filterRow.getCell(propertyId);
            if (headerCell != null) {
                if (!"idFacility".equals(propertyId) && !"facilityImageExists".equals(propertyId) && !"disabled".equals(propertyId)) {
                    headerCell.setComponent(createFilterTextField(propertyId));
                } else if ("facilityImageExists".equals(propertyId) || "disabled".equals(propertyId)) {
                    headerCell.setComponent(createFilterComboBox(propertyId));
                }
            }
        }
    }

private TextField createFilterTextField(final Object propertyId) {
        final TextField tfFilter = new TextField();
        tfFilter.setInputPrompt(tp.getText(TextKey.VIEW_TEXTFIELD_FILTER));
        tfFilter.addStyleName(ValoTheme.TEXTFIELD_TINY);
        tfFilter.setWidth(100, Unit.PERCENTAGE);
        tfFilter.addTextChangeListener(event -> {
            final String text = event.getText();

            bicGridFacility.removeContainerFilters(propertyId);
            if (!text.isEmpty()) {
                bicGridFacility.addContainerFilter(new SimpleStringFilter(propertyId, text, true, false));
            }
            bicGridFacility.sort(new Object { "facilityName" }, new boolean { true });
        });
        return tfFilter;
    }

@SuppressWarnings("unchecked")
    private ComboBox createFilterComboBox(final Object propertyId) {
...
}

So you see i was able to remove the container filter for an explicit property id and if the text of the filter field is not empty i am setting an new container filter. This was very easy and not complicated.

In Vaadin 8 i am not able to do this. I try to get this done in the same way but there are some obstructions.

private void addGridFilters() {
        final HeaderRow filterRow = gridFunctionalAreaType.appendHeaderRow();
        for (final Column<FunctionalAreaTypeDto, ?> column : gridFunctionalAreaType.getColumns()) {
            final HeaderCell headerCell = filterRow.getCell(column);
            if ("idFunctionalAreaType".equals(column.getId())) {
                headerCell.setComponent(createFilterTextField(column));
            } else if ("functionalAreaTypeImageExists".equals(column.getId())) {
                // headerCell.setComponent(createFilterComboBox(headerCell.getColumnId()));
            }
        }
    }

    private TextField createFilterTextField(final Column<FunctionalAreaTypeDto, ?> column) {
        final TextField tfFilter = new TextField();
        tfFilter.setPlaceholder(tp.getText(TextKey.VIEW_TEXTFIELD_FILTER));
        tfFilter.addStyleName(ValoTheme.TEXTFIELD_TINY);
        tfFilter.setWidth(100, Unit.PERCENTAGE);
        tfFilter.addValueChangeListener(event -> {
            final ValueProvider<FunctionalAreaTypeDto, String> valueProvider = (ValueProvider<FunctionalAreaTypeDto, String>) column.getValueProvider();

            listDataProviderFunctionalAreaType.addFilter(valueProvider, functionalAreaId -> {
                return functionalAreaId.toLowerCase().contains(event.getValue().toLowerCase());
            });
        listDataProviderFunctionalAreaType.refreshAll();
        });
        return tfFilter;
    }

I loop over the columns and get the headercell. and if its the right column i generate a textfield or a combobox. For this i have to sett an id for the column. This is a pity because i was hoping with vaadin 8 to do whitout a string in the class. In the create method i try to set with the valueprovider of the column the SerializablePredicate with addfilter.

The problem here is i can't set in addfilter the Valueprovider directly with "column.getValueProvider()" because it returns "SerializableFunction<T, ? extends V>" and not a valueProvider. So i have to cast it. The other problem is i can't remove a filter. i am missing "removefilter(ValueProvider<T, V> valueProvider)". i only can call "clearFilters" for all filters. so the other filters won't be active anymore.

So my question here is, am i missing some things or is my use case not possible in the moment? I think there are some methods missing to get this done!

Leif Åstrand
5 years ago Mar 31, 2017 11:40am

Sebastian Filke: The other problem is i can't remove a filter. i am missing "removefilter(ValueProvider<T, V> valueProvider)". i only can call "clearFilters" for all filters. so the other filters won't be active anymore.
 
So my question here is, am i missing some things or is my use case not possible in the moment? I think there are some methods missing to get this done!

My suggestion for you would be to attack the problem from the opposite direction: Use a single filter instance that takes care of all columns. The filter implementation can iterate through all columns and determine whether each item should be included based on the value of the filter component in that column's header (if any). You'd then also have to call dataProvider.refreshAll() whenever the value in any filtering component changes so that e.g. Grid can know that it should fetch a new set of items from the data provider. It might feel inefficent to do refreshAll() every time a filter value changes, but this is also what all addFilter methods internally do.

Methods for removing filters are left out on purpose because each lambda expression is unique. This in turn means that something like myDataProvider.removeFilter(foo -> false) wouldn't remove a filter added using myDataProvider.addFilter(foo -> false). The different overloads of addFilter would further complicate the way filters are removed. What we could do is to use the same pattern that we use for removing event listeners, i.e. that addXyzListener methods return a registration instance that can be used for removing that particual listener. I created https://github.com/vaadin/framework/issues/8985 about this.

Sebastian Filke: The problem here is i can't set in addfilter the Valueprovider directly with "column.getValueProvider()" because it returns "SerializableFunction<T, ? extends V>" and not a valueProvider. So i have to cast it.

Oops. I created a Pull Request for that type inconsistency: https://github.com/vaadin/framework/pull/8983

Sebastian Filke: I loop over the columns and get the headercell. and if its the right column i generate a textfield or a combobox. For this i have to sett an id for the column. This is a pity because i was hoping with vaadin 8 to do whitout a string in the class.

Do you have any other approach in mind that would allow identifying columns without relying on a (string) id?

Sebastian Filke
5 years ago Apr 13, 2017 10:09am