Skip to content
Rodja Trappe edited this page Aug 9, 2023 · 6 revisions

Introduction

In this a step-by-step tutorial, we'll learn how to create a "search as you type" cocktail lookup using NiceGUI. The website will allow users to type in the name of a cocktail, and as they type, it will display matching cocktails with their images. The full example can be fetched from the repository.

Prerequisites

  • Have Python 3.8 or greater installed.
  • Install NiceGUI with pip install nicegui.

First Version

1. Import Necessary Libraries

Create a file main.py in an empty directory and import the necessary modules:

import asyncio
from typing import Optional
import httpx
from nicegui import events, ui

2. Setup the API Client

We'll be using httpx to make asynchronous HTTP requests to the cocktail database. Read more about why async is crucial in our FAQ.

api = httpx.AsyncClient()

3. Basic Search Function

Let's first create a simple version of the search function, without handling edge cases or optimizing the user experience.

async def search(e: events.ValueChangeEventArguments) -> None:
    '''Search for cocktails as you type.'''
    response = await api.get(f'https://www.thecocktaildb.com/api/json/v1/1/search.php?s={e.value}')
    results.clear()
    with results:  # enter the context of the the results row
        for drink in response.json()['drinks'] or []:  # iterate over the response data of the api
            with ui.image(drink['strDrinkThumb']).classes('w-64'):
                ui.label(drink['strDrink']).classes('absolute-bottom text-subtitle2 text-center')

4. Create the User Interface

The user interface is quite simple: a search field and a horizontal row which automatically breaks if there are too many results.

search_field = ui.input(on_change=search).props('outlined rounded').classes('w-96')
results = ui.row()

With the .props builder function you can add html attributes which are in this case interpreted by Quasar to alter the appearance of the input element. The .classes builder function can add css classes. Because NiceGUI comes with TailwindCSS, the w-96 can be used to define the width. Note that the results row is cleared in the search function before adding new content.

5. Launch

By adding ui.run() at the end of the file, and run pyhton3 main.py

Improve Usability and Look

While the first version works as expected, we can do better by styling and handling corner cases.

1. Position the Search Bar

By adding the autofocus prop the input field is ready for typing after page load. item-aligned input-class="ml-3" adjusts the position of the entry field in relation to the border. The tailwind classes self-center mt-24 are used to position the search bar on the page and transition-all animates the changes of css events.

search_field = ui.input(on_change=search) \
    .props('autofocus outlined rounded item-aligned input-class="ml-3"') \
    .classes('w-96 self-center mt-24 transition-all')

After the search results are displayed, we want to move the search field up to make space for more results. We'll adjust the search field's position based on the results.

search_field.classes('mt-2', remove='mt-24')

Insert this line above the clearing of the results.

2. Handle Empty Responses

Sometimes the search might not yield any results, or there might be an issue with the response. To ensure our application doesn't break, we'll simply exit the search function in these cases before adding the content in with results.

if response.text == '':
    return

3. Cancel Previous Queries for Faster Typing

When a user types quickly, we don't want to wait for each character's search query to finish. We can improve responsiveness by canceling the previous query if a new character is typed.

First, we'll introduce a global variable running_query to keep track of the current search task.

api = httpx.AsyncClient()
running_query: Optional[asyncio.Task] = None

In our search function we can check this variable, cancel a previous task and assign the new one:

async def search(e: events.ValueChangeEventArguments) -> None:
    '''Search for cocktails as you type.'''
    global running_query
    if running_query:
        running_query.cancel()  # cancel the previous query; happens when you type fast
    search_field.classes('mt-2', remove='mt-24')  # move the search field up
    results.clear()
    # store the http coroutine in a task so we can cancel it later if needed
    running_query = asyncio.create_task(api.get(f'https://www.thecocktaildb.com/api/json/v1/1/search.php?s={e.value}'))
    response = await running_query
    if response.text == '':
        return

Note that we need to tell Python that the running_query is a global variable. Otherwise the assignment would not work.

Conclusion

With these steps, you've successfully created a "search as you type" cocktail lookup using NiceGUI. You can now use this approach to create other real-time search applications or even extend this one by adding more features like detailed cocktail recipes, ingredients, and more.