MultiSelectListBox all items get deselected after a filter is added to the

Hi all,

I’m using Vaadin 14.2.1 (tried also with older versions) and I faced some strange behavior when using the MultiSelectListBox. I don’t know if that’s a feature or a bug, but I have the following usecase - I want to filter the data in the data provider of the MultiSelectListBox and every time the filter is applied, all of the selected items get deselected. I noticed that this happens when refreshAll method of the data provider gets called (it gets called also when a filter is set).

It can be easily reproduced with this code snippet. If the filter is not set, then item “5” will be selected when the component is added to the layout:

MultiSelectListBox<String> stringMultiSelectListBox = new MultiSelectListBox<>();
stringMultiSelectListBox.setItems(Arrays.asList("1", "2", "3", "4", "5"));
stringMultiSelectListBox.select("5");
((ListDataProvider<String>)stringMultiSelectListBox.getDataProvider()).setFilter(s -> s.equals("5"));
add(stringMultiSelectListBox);

Have anyone faced this issue and is there a way to prevent deselecting all items on filtering?

Hello Gero,

I’m facing the same issue with the latest 14.3.1.

Have you been able to find a solution/workaround for this issue?
Or have you already reported it as an issue as I believe this is a bug on Vaadin’s side?

Ok, I have just found a workaround for now.
I am maintaining another Collection of selected items.

After filtering the items, I match the items that have been filtered with the List of selected items and keeping only the one available in the filtered list and reapply the selection.

I also added a Selection Listener to track which items are being added/removed outside the filtering operation.
So my code looks like this:

	// boolean used to control filtering and internal selection of Multi select List Box
	private boolean isFiltering = false;
	
	private Component initRoleListBox()
	{

		// Collection to keep selected items
		final Set<String> assignedValues = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
		assignedValues.addAll(Arrays.asList("Test1", "Test4"));

		// Collection with List of available Items
		final Set<String> availableValues = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
		availableValues.addAll(Arrays.asList("Test1", "Test2", "Test3", "Test4", "Test10"));

		// Build the MultiSelectListBox using ListDataProvider
		final ListDataProvider<String> listProvider = new ListDataProvider<>(availableValues);
		final MultiSelectListBox<String> stringListBox = new MultiSelectListBox<>();
		stringListBox.setDataProvider(listProvider);
		
		// Pre select values
		stringListBox.select(assignedValues);
		
		// Selection Listener to control the List of currently assigned 
		stringListBox.addSelectionListener(l -> {
			// Ignore selection changes triggered by filtering
			if (!this.isFiltering) {
				assignedValues.addAll(l.getAddedSelection());
				assignedValues.removeAll(l.getRemovedSelection());
			}
		});
		
		// Filter Text Field
		final TextField filter = new TextField();
		filter.setValueChangeMode(ValueChangeMode.EAGER);
		filter.addValueChangeListener(e -> {
			// set filtering to true to notify selection listener to ignore changes
			this.isFiltering = true;
			
			// apply the filter
			listProvider.setFilter(s -> s.toLowerCase().contains(e.getValue()));
			
			// find matching items available in the filtered list and currently assigned items
			final Set<String> matchingItems = listProvider.fetch(new Query<>(listProvider.getFilter()))
				.filter(assignedValues::contains)
				.collect(Collectors.toSet());
			
			// re-apply the selected items if any available in the filtered list
			if (!matchingItems.isEmpty()) {
				stringListBox.select(matchingItems);
			}
			
			// reset filtering to false so that selection listener can process selections
			this.isFiltering = false;
		});
		
		add(filter, stringListBox);
	}

Hope this helps someone as I spent quite some time trying to find a workaround.
If anybody has a better way of achieving the desired behaviour, please let me know.

I still think this is an issue on MultiSelectBox side though, the same filter works perfectly fine with grid without loss os selection while filtering!

Hi Alban,

Thanks for your reply. I also did some investigation and I copied the classes in my project, so I can find a solution easier and create an issue in the gitHub repo. The problem comes from the ListBoxBase, because there are attached some listeners to the dataProvider and the items are refreshed on DataRefreshEvent, but the filtering triggers a DataChangeEvent, which rebuilds the component and clears the selection.

private void rebuild() {
        clear();
        removeAll();
		...
    }

I created a new method, which refreshes all items, doesn’t clear the selection and I use the default one only when the component is initialized.

I thought about reporting this, but then I faced the second problem - the selection is based on index. That means that if you select something and filter, the same positions will be selected after that, but you will already have different items.

I found a solution for that as well by changing the presentationToModel and modelToPresentation methods in the MultiSelectListBox:

private static <T> Set<T> presentationToModel(final MultiSelectListBox<T> listBox, final JsonArray presentation) {

        // If the data provider is filtered and an item is selected, but not shown, it has an index -1, so we have to exclude it
        final Set<T> selectedValues = IntStream.range(0, presentation.length())
            .map(i -> (int) presentation.getNumber(i))
            .filter(i -> i >= 0)
            .mapToObj(index -> listBox.getItems()
                .get(index))
            .collect(Collectors.toSet());

        // We search for values which are selected, but not in the data provider in case of filtering
        final Set<T> valuesToAdd = listBox.getValue()
            .stream()
            .filter(value -> !listBox.getItems()
                .contains(value))
            .collect(Collectors.toSet());

        selectedValues.addAll(valuesToAdd);

        return Collections.unmodifiableSet(selectedValues);
    }

    private static <T> JsonArray modelToPresentation(final MultiSelectListBox<T> listBox, final Set<T> model) {
        final JsonArray array = Json.createArray();
        model.stream()
            .map(listBox.getItems()::indexOf)
            .forEach(index -> array.set(array.length(), index));
        return array;
    }

I also call setPresentationValue(getValue()) in the new refresh method to update the frontend after filtering.

However… this works, but I gave up reporting this. The component is not meant to be used for my case. The last problem (which is unsolvable on my side and I read, that the component is “designed” to work like this and from Vaadin don’t plan to change it) was that all the items are rendered eagerly in the frontend. With 100 items it’s a bit slow, when I have 1000 items it takes around 2-3 seconds to show them, with 10000 the application is unsuable, So I decided to throw away that in the garbage and use a grid with a single column. I styled it to look the same and it works perfect.

If your interested in my solution, or have any questions, feel free to ask.

Thanks Gero.

If I couldn’t get around this problem, I would have also considered the grid option with custom renderers.

My list will be quite small, no more than 50 items, so I’m happy to use an extra Collection to keep track of selected elements and performance should not be affected too much.

And who knows, this issue might be picked up one day by the development team.

Thanks again!

I created this issue regarding the behavior of MultiSelectListBox component https://github.com/vaadin/vaadin-list-box/issues/87 (which is similar to the case of Select https://github.com/vaadin/vaadin-select/issues/239 but different from that of others components such as ComboBox)