Search form with multiple search fields

Hello,

I am wondering why I don’t find a comparable question but anyway.

I want to create an form with multiple fields to search through an entity. It should be possible not to fill all the fields and also search over child attributes. The result should be displayed in a grid.

The main problem for me is the searching part. How can this be implemented in Vaadin 8, especially if several fields are filled but not all?

Thanks for any advice!

Anyone who could help, please?

There are many ways to achieve something like that. One option would be to use an explicit DataProvider for the grid and use the search terms to filter the DataProvider, like described here: https://vaadin.com/docs/v8/framework/datamodel/datamodel-providers.html

Thanks for your answer.

I have created now an DataProvider for my entity, but I cant get it worked for multiple search fields.

What I have so far:

public class PersonDataProvider extends AbstractBackEndDataProvider<Person, String>{
	@Override
    @Transactional
    public int sizeInBackEnd(Query<Person, String> t) {
        return (int) getItems(getPaging(t).pageable, getFilter(t)).count();
    }

    private String getFilter(Query<Person, String> t) {
        return t.getFilter().orElse(null);
    }

    @Override
    @Transactional
    public Stream<Person> fetchFromBackEnd(Query<Person, String> t) {
        PageQuery pageQuery = getPaging(t);
        return getItems(pageQuery.pageable, getFilter(t))
                .skip(pageQuery.pageOffset).limit(t.getLimit());
    }

	private Stream<Person> getItems(Pageable page, String filterText) {
        if (filterText == null || filterText.isEmpty()) {
            return StreamSupport.stream(personRepo.findAll(page).spliterator(), false);
        }
        String filter = filterText.toLowerCase(UI.getCurrent().getLocale());
        return personRepo
                .findDistinctByNameContainingIgnoreCaseOrGenderIgnoreCase(
                        filter, filter, page)
                .stream();
    }

and for my UI following:

	Grid<Person> grid = new Grid(Person.class);
	filterDataProvider = personDataProvider.withConfigurableFilter();
	grid.setDataProvider(filterDataProvider);
	filterName = new TextField();
	filterName.addValueChangeListener(event -> filterDataProvider.setFilter(event.getValue()));
	filterGender = new TextField();
	filterGender.addValueChangeListener(event -> filterDataProvider.setFilter(event.getValue()));

How can I seperate now the filter input into several textfields? I just have one method to get the items.

Would be great if somebody could help.

Currently, your Filter type in the DataProvider is String: AbstractBackEndDataProvider<Person, **String**>. You can also use a more complex object there, so you can put multiple fields in it. Then use the fields in the DataProvider implementation to do the actual filtering.

-Olli

Can you give a short code example? Thank you!

Here’s one from the framework tests: https://github.com/vaadin/framework/blob/master/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java#L35

It’s maybe a bit dense, but the main point is that in this case Query.getFilter() doesn’t return a String (or an Optional), it returns another type of object. You can thus use that that object to do your own filtering.

Sorry, now I am more confused then before. How can I filter then with that object? Don’t know how to adapt this example to my case.

Okay, I have now changed the String attribute to the object Person and use PersonService to filter.

public class PersonDataProvider extends AbstractBackEndDataProvider<Person, Person>{
	...
	private Stream<Person> getItems(Pageable page, Person filterPerson) {
        if (filterPerson == null) {
            return StreamSupport.stream(personService.findAll(page).spliterator(), false);
        }        
        return personService.findAnyMatchingPerson(filterPerson, page).stream();
    }
}

PersonService:

public List<Person> findAnyMatchingPerson(Person filterPerson, Pageable pageable) {
	if (filterPerson.getName() != null && !filterPerson.getName().equals("")) {
		if (filterPerson.getGender() != null && !filterPerson.getGender().equals("")) {
			return getRepository().findByNameIgnoreCaseAndGenderIgnoreCase(filterPerson.getName(), filterPerson.getGender(), pageable);
		} else {
			return getRepository().findByNameIgnoreCase(filterPerson.getName(), pageable);
		}
	} else {
		if (filterPerson.getGender() != null && !filterPerson.getGender().equals("")) {
			return getRepository().findByGenderIgnoreCase(filterPerson.getGender(), pageable);
		} else {
			return getRepository().findAll();
		}
	}
}

This is just with two attributes, but I want to add a lot more. There must be an better solution?!
Otherwise I have to create tons of methods in the repository and check all the possible cases in the service like above.

Yes, you should probably write the queries in SQL / JPQL instead.

I use now just one method of my repository:

@Query("SELECT p FROM Person p WHERE "
		+"(:name is null or :name='' or p.name = :name) and "
		+"(:gender is null or :gender='' or p.gender = :gender)")
List<Person> findAnyMatchingPerson(@Param("name") String name, 
		@Param("gender") String gender);

I suppose that this is not the best way to do it, but it works.