Creating an AutoComplete textbox using Hilla's ComboBox

Hi there! On the doc page regarding the ComboBox component it is mentioned that items can be loaded lazily.

In my Java backend, I have a @BrowserCallable/@Endpoint service that implements CrudService and CountService.

I want to load items of the ComboBox lazily and additionally the ability to filter items with the text input possibility of ComboBox.

So far, I have seen the property dataProvider of the ComboBox component and have implemented a simple solution that is far from ideal:

const helper = async (params: ComboBoxDataProviderParams, callback: ComboBoxDataProviderCallback<any>) => {
    const items = await ItemEndpoint.list(
      {
        pageNumber: params.page,
        pageSize: params.pageSize,
        sort: {
          orders: []
        }
      },
      undefined // How to construct Java-Filter based on params.filter?
    );
    callback(items, await ItemEndpoint.count(undefined));
  };
...
return (
    <ComboBox dataProvider={helper} itemLabelPath="name" itemValuePath="id" />
);

This solution is bad because it is not generic enough to allow working with any @BrowserCallable/@Endpoint that implements ListService and CountService.

Furthermore, is there any utility on the Hilla side to provide a Java Filter (second parameter of list) based on the params.filter user input? As you see in above code, it is currently undefined.

Could please you provide an example on how to do this the Vaadin way? Especially as generic as possible to be compatible with any @BrowserCallable/@Endpoint that implements ListService and CountService.

Thanks in advance!

You can create a filter in the following way:

    const filter: PropertyStringFilter = {
      '@type': 'propertyString',
      propertyId: 'name',
      matcher: Matcher.CONTAINS,
      filterValue: params.filter,
    };

This approach requires you to decide the property to filter on in the template and not in the endpoint.

If you would want to filter in the endpoint instead, you could do add endpoint methods like

    private Filter filterFromString(String filterString) {
        PropertyStringFilter filter = new PropertyStringFilter();
        filter.setFilterValue(filterString);
        filter.setMatcher(Matcher.CONTAINS);
        filter.setPropertyId("name");
        return filter;
    }

    public List<Product> listWithFilter(Pageable pageable, String filterString) {
        return list(pageable, filterFromString(filterString));
    }

    public long countWithFilter(String filterString) {
        return count(filterFromString(filterString));
    }

and then the helper would be more like

  const helper = async (params: ComboBoxDataProviderParams, callback: ComboBoxDataProviderCallback<any>) => {
    const items = await ProductService.listWithFilter(
      {
        pageNumber: params.page,
        pageSize: params.pageSize,
        sort: {
          orders: [],
        },
      },
      params.filter,
    );
    callback(items, await ProductService.countWithFilter(params.filter));
  };

Do not that using the Filter class here is in no way mandatory. It is intended as a way for the client side to provide filtering information to the endpoint, e.g. when you have a grid with filtering components in the header cells.

Thank you very much! That was very helpful! Two more question:

  1. Is it possible to debounce the input before sending a server request? Right now, two requests are sent on every key stroke, which might be cumbersome if many users are typing. Maybe adding a 500-800ms delay after the typing has stopped before making a server request would be better.
  2. The code above is good when used in a create form. However, in an edit form, the combobox has to be preloaded with the corresponding data. Let’s say an employee is edited and the departmentId is set inside the form. Searching for departments is possible and setting the departmendId of the employee is also possible. However, when first loading the page, the departmentid ComboBox has to be pre-filled with the corresponding department. How would one do this?
  1. There is an issue about automatic debouncing, I think it also contains a workaround that you can use in your data provider [combo-box] Add debounce to data requests · Issue #518 · vaadin/web-components · GitHub
  2. It sounds like you want an eagerly populated combo box, something like e.g.
  const [items, setItems] = useState<Product[]>([]);

  useEffect(() => {
    ProductService.listAll().then((products) => setItems(products));
  }, []);

...
        <ComboBox itemLabelPath="name" items={items}></ComboBox>

Not quite eagerly. I want the current value of the entity to be preselected if there is any. Changing that value of the entity should still be accomplished with lazy loading, as there might be thousands of values to choose from.