Autocomplete Widget with Dynamic Options Fetching

Hi, this is something I’ve always wanted to implement but couldn’t quite work out how.

Say I want to use an AutocompleteInput widget but I want the options to be fetched dynamically from a search of that input.

E.g. the user inputs “hello” and I have an API endpoint that I can call with this string that will return a list of potential matches. I.e. an external search method.

I wondered about subclassing the widget and adding a depends on the value_input e.g.

class FetchAutocomplete(pn.widgets.AutocompleteInput):
    """Widget to autocomplete by fetching options."""

    @pn.cache(ttl=3600, max_items=25)
    async def _fetch_options(self, value: str) -> list[str]:
        """Fetch the options for the widget."""
        try:
            async with MyClient() as client:
                response: dict = await client.find(value) 
        except httpx.HTTPError as error:
            return []

        return [result["name"] for result in response["results"]]

    @param.depends("value_input", watch=True)
    async def _update_options(self):
        """Update the options for the widget."""
        if len(self.value_input) < self.min_characters:
            return

        self.options = await self._fetch_options(self.value_input)

Now this somewhat works but the problem is that this is not in sync with the user input. As soon as the input is entered it does the search against the current options. The options are updated they are not refreshed on the input. Enter one more character and then it will present the options that match from the previous search.

This makes sense to me as to what’s going on, but I can’t quite work out how to trigger a refresh of the matches after the options have been updated, if it is even possible?

Of course there might be a better way to do this completely?

Thanks for any help with this.

I think you might have to introduce some JS callback, or use JSComponent.

A reproducible version of your code, albeit still not working as intended.

import panel as pn
import param
import asyncio
import httpx

pn.extension()

class FetchAutocomplete(pn.widgets.AutocompleteInput):
    min_characters = param.Integer(default=2)

    @pn.cache(ttl=3600, max_items=25)
    async def _fetch_options(self, value: str) -> list[str]:
        """Simulate fetching options from an API."""
        await asyncio.sleep(0.2)  # Simulate latency
        return [f"{value}_{i}" for i in range(5)]

    @param.depends("value_input", watch=True)
    def _on_input_change(self):
        if len(self.value_input or "") >= self.min_characters:
            asyncio.create_task(self._update_options_and_refresh(self.value_input))

    async def _update_options_and_refresh(self, value):
        # fetch options
        new_options = await self._fetch_options(value)
        self.options = new_options


# Usage
autocomplete = FetchAutocomplete(placeholder="Type to search...")
pn.Column(autocomplete).show()

Maybe you can ask some LLM to somehow introduce some JS callback to expand the options on typing.

Giving this a shameless one time bump to see if anyone has more skill than me to be able to do this in a custom manner.

As said above (thanks @ahuang11) I think this would be doable with a custom built widget like those detailed here: Create Custom Widgets using ESM Components — Panel v1.7.4 but I struggle with trying to essentially rebuild the AutocompleteInput widget. Tried the usual LLMs but still can’t quite get to what I want.

I’m definitely lacking in my JS knowledge as to how to trigger the refresh, but also can’t seem to get that far in defining a custom widget that behaves like the existing one. Is it possible to customise existing widgets?

Would be great to have for my use case!

Can you submit an feature request to GitHub to have a dynamic callable for Autocomplete? Maybe even in Bokeh…

1 Like