Vaadin 8 add filter to each column of grid dynamically

Hallo,
i couldn’t find an answer to the question whether it is possible to add a textField filter for each headerRow column of a grid.
So far I’m adding a filter seperatly for each column like this:

HeaderRow filterRow = someGrid.appendHeaderRow();
	// First filter
	TextField filterName = new TextField();
	filterName.addValueChangeListener(event -> dataProvider.addFilter(
			person -> StringUtils.containsIgnoreCase(person.getName), filterName.getValue())));
	filterItemKey.setValueChangeMode(ValueChangeMode.EAGER);
	filterRow.getCell("name").setComponent(filterName);

My problem is that i have an grid with 100 columns so this is realy a lot of work. Is there an easier way off adding a filter to each column?

Hi Jan

I went through the same situation as you. First of all, I think your current approach does not really work - the reasons for this I explained in [this post]
(https://vaadin.com/forum/thread/17591983/17592893). In short, every change in any of your filter fields will add a filter to the dataProvider. After some valuechanges, there will be too many contradicting filters (the filters are added with AND not OR - all added filters must match an item for it to show up), which will soon result in no matching items. Key sentence in linked post:

for every filter field, add the same ValueChangeListener, which will take all filter values into account when it sets the new filter (with setFilter! let only one filter be active at any time but let that filter include all values you want to filter by)

My solution - also explained in above link - will not only fix abovementioned shortcoming of your solution, but also will it be less code to write for each column filter (you still have to create the filter fields. But you could write a a method for that so your code still looks nice and non-repetitive). If you have questions about my approach, feel free to ask.

// filter fields must be class fields, to access them in onFilterChange
private TextField nameFilter, addressFilter;

private void createFilterFields(){
	HeaderRow filterRow = someGrid.appendHeaderRow();
	
	// for each column you want to filter, create a filter field here like so:
	nameFilter = singleFilterTextField("name", filterRow); 
	addressFilter = singleFilterTextField("address", filterRow); // added another filter to show how multiple filters work
}
private TextField singleFilterTextField(String cellName, HeaderRow filterRow){
	TextField filterField = new TextField();
	filterField.setValueChangeMode(ValueChangeMode.EAGER);
	filterField.setWidth("100%");
	filterField.addValueChangeListener(this::onFilterChange);
	filterRow.getCell(cellName).setComponent(filterField);
	return filterField;
}
private void onFilterChange(HasValue.ValueChangeEvent event){
	ListDataProvider<FooBar> dataProvider = (ListDataProvider<FooBar>) getDataProvider();
	dataProvider.setFilter((item) -> {
		// for each filter field, add a line to check if that filter matches the item
		boolean nameDoesMatch = item.getName().contains(nameFilter.getValue());
		boolean addressDoesMatch = item.getAddress().contains(addressFilter.getValue());

		return nameDoesMatch && addressDoesMatch;
	});
}

Thank you Kaspar for you fast answer.
You are right, with your solution I will need a lot less code, but i was looking for something like in Vaadin 7. There you could just do:

	private void createFilterForEachGridColumn(HeaderRow headerRow) {
		for (Column column : grid.getColumns()) {
			// get propretyId of column
			final Object pid = column.getPropertyId();
			// get header cell of column
			HeaderCell cell = filterRow.getCell(pid);

			final TextField filterField = new TextField();
			cell.setComponent(filterField);
			filterField.addTextChangeListener(new TextChangeListener() {
			beanItemContainer.removeContainerFilters(pid); // forgot this (removes the old filter)
				@Override
				public void textChange(TextChangeEvent change) {
						Filter	filter = new SimpleStringFilter(pid, change.getText(), true, false);
						beanItemContainer.addContainerFilter(filter);
					}
				}
			}
	}

I tried to do this the same way with Vaadin 8 but struggling to do so.

Yeah, the addFilter-vs-setFilter problem that I mentioned basically makes it impossible to do that. I am not an official Vaadin dev so don’t take my word on it though. But I see no way to do it this way, while ensuring that there is no outdated filter being applied. I haven’t used Vaadin 7 but this code looks as if it has the very same problem - calling add filter every time a filterfield is changed, but never removing older/outdated filters.

Kaspar, thanks a lot! Finally an explanation that can be followed. What I missed was simply this:

dataProvider.setFilter((item) -> {
		// for each filter field, add a line to check if that filter matches the item
		boolean nameDoesMatch = item.getName().contains(nameFilter.getValue());
		boolean addressDoesMatch = item.getAddress().contains(addressFilter.getValue());

		return nameDoesMatch && addressDoesMatch;
	});
});

A proper example of the setFilter-Method. I used this with addFilter to filter different columns with different filters. Thanks again!

Oleg Wahl:
I used this with addFilter to filter different columns with different filters.

What do you mean? I wrote this specifically to avoid usage of addFilter, and explained in detail why using addFilter is bad. :slight_smile:
If you have found a working solution using addFilter, can you please explain it?

Okay, my situation is that I have a Grid with 3 columns, two with Strings and one with a Date. I have a filter for each.

Each filter-field has a ValueChangeListener which calls updateFilters([all three filter-fields]
). In that method, I run myListDataProvider.clearFilters() and then just add the filtering anew with addFilter for each filter-field that is not empty. This prevents having outdated filters, or filters stacking up.

This sounds slow, but works very quickly on three columns. However, this of course has nothing to do with “adding filters dynamically” – I just needed three fixed filters. So I’m not sure if that will help you, but it does just what I wanted.

Here are the simplified critical parts of one filter and its updateFilters-part.

TextField benFilter = new TextField();
filterRow.getCell(...).setComponent(benFilter);
//...

benFilter.addValueChangeListener(event -> {
	updateFilter(..., ..., benFilter);
});

//...

private void updateFilter(..., ..., TextField benFilter) {
	myListDataProvider.clearFilters();
	//...
	Optional<String> optBen = benFilter.getOptionalValue();
	if (optBen.isPresent()) {
		String ben = optBen.get().trim();
		if (!"".equals(ben)) {
			myListDataProvider.addFilter((item) -> {
				 return item.getBen().contains(ben);
			});
		}
	}
	//...
}
	

In short, I remove and reset all filters if any of the filter-fields change.

(Edit: I don’t know why the indentation is partly lost…sorry)

Aha now I get it. It is basically the same idea as my solution; Each time any filter value changes, all filters will be reapplied.
While I do all my filter logic in one single filter using setFilter, you split it up into separate filters, using addFilter. As long as you call clearFilters each time, this will work just as well.

Thanks for the clarification

PS: my solution is not restricted to TextField filters, you can use it with DatePicker and ComboBox and everything that extends HasValue. Instead of calling the function singleFilterTextField you would call for example singleFilterDatePicker. The DatePicker can still set the same value change listener (onFilterChange), because it expects a generic HasValue.ValueChangeEvent.