-
First Check
Example Code"""Widgets used in the search page."""
from dataclasses import field
from typing import List
import itertools
import string
from nicegui.observables import ObservableList
from nicegui import ui, app, binding
@binding.bindable_dataclass
class SearchState():
"""Holds the state specific to the search page."""
is_searching: bool = False
queries: ObservableList[str] = field(default_factory=ObservableList)
class SearchUI:
def __init__(self):
self.state = SearchState()
self.validation ={
'Length >= 3': lambda value: all(len(v.strip()) >= 3 for v in value)
}
@ui.refreshable
def add_search_bar_as_chips(self, options: List[str]) -> ui.input_chips:
with ui.row().classes('w-full h-full justify-center p-4'):
search_bar = ui.input_chips(
label="Search with keywords...",
validation=self.validation
).props('clearable rounded outlined dense') \
.classes('w-full max-w-2xl') \
.bind_value(self.state, 'queries')
with search_bar.add_slot('prepend'):
ui.icon('search', color='primary')
return search_bar
@ui.refreshable
def add_search_bar_as_select(self, options: List[str]) -> ui.select:
"""
Create a search input styled as chips but with a dropdown for autocompletion.
"""
with ui.row().classes('w-full h-full justify-center p-4'):
# Build ui.select as a ui.input_chips.
# We do this to enable filtering with the dropdown of ui.select.
# Reference how ui.input_chips is built:
# https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/input_chips.py#L44
# Note that some of its props are arguments to ui.select. Others have to be set with .props()
#
# 1.st .prop(): Missing props to make it ui.input_chips
# 2.nd .prop(): Styling props
search_bar = ui.select(
label="Search with keywords...",
options=options,
multiple=True, new_value_mode="add-unique",
validation=self.validation,
# Parameter with_input=True enables the following props: use-input, fill-input, input-debounce-0
# Reference: https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/select.py#L73
with_input=True,
).props('use-chips hide-dropdown-icon clearable') \
.props('rounded outlined dense') \
.classes('w-full max-w-2xl') \
.bind_value(self.state, 'queries')
with search_bar.add_slot('prepend'):
ui.icon('search', color='primary')
return search_bar
async def perform_search(self) -> None:
pass
@ui.page('/')
def page():
search = SearchUI()
n = 3
options = [
f"{''.join(letters)} {i}"
for letters in itertools.product(string.ascii_uppercase, repeat=3)
for i in range(1, n+1)
]
ui.label(f"{len(options)=}")
search.add_search_bar_as_chips(options=options)
search.add_search_bar_as_select(options=options)
ui.run(reload=True)DescriptionI am building a search widget for my application that uses keywords to then build a query to retrieve the results. I I like the idea of Since I have an index anyway I thought that it's very useful for my users to have an autocomplete function. I replicated the look of The issue I run into: The quasar select documentation shows an example of a behaviour I am looking for at Do you have another suggestions to create something similar with other elements? I am considering adapting NiceGUI's search.py. NiceGUI Version3.0.3 Python Version3.12.10 BrowserChrome Operating SystemWindows Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
You tried updating options rather than setting all at once? Ref: https://nicegui.io/documentation/select#update_options Use
You could also use the filter on event but I think it will require JS code to handle filters. I don't know how to use virtual scroll, though i believe it may require JS as well. Above is prolly the most Pythonic way to achieve what you want. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you @BaccanoMob, that was a useful hint. I have not touched events and methods of Quasar objects yet. It was a nice opportunity to learn. Overall, this is pretty much what I wanted (plus extra stuff like triggering validation again and fixing other surprising errors that occured): from dataclasses import field
from typing import List
import itertools
import string
from nicegui.observables import ObservableList
from nicegui import ui, app, binding, events
@binding.bindable_dataclass
class SearchState():
queries: ObservableList[str] = field(default_factory=ObservableList)
class SearchUI:
def __init__(self):
self.state = SearchState()
self.validation ={
'Length >= 3': lambda value: all(len(v.strip()) >= 3 for v in value)
}
@ui.refreshable
def add_search_bar(self, options: List[str]) -> ui.select:
"""
Create a search input styled as chips but with a dropdown for autocompletion.
"""
MAX_OPTIONS_TO_SHOW = 10
with ui.row().classes('w-full h-full justify-center p-4'):
# Build ui.select as a ui.input_chips.
# We do this to enable filtering with the dropdown of ui.select.
# Reference how ui.input_chips is built:
# https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/input_chips.py#L44
# Note that some of its props are arguments to ui.select. Others have to be set with .props()
#
# 1.st .prop(): Missing props to make it ui.input_chips
# 2.nd .prop(): Styling props
search_bar = ui.select(
label="Search with keywords...",
options=self.state.queries,
multiple=True, new_value_mode="add-unique",
validation=self.validation,
# Parameter with_input=True enables the following props: use-input, fill-input, input-debounce-0
# Reference: https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/select.py#L73
with_input=True,
).props('use-chips hide-dropdown-icon clearable') \
.props('rounded outlined dense') \
.classes('w-full max-w-2xl') \
.bind_value(self.state, 'queries')
with search_bar.add_slot('prepend'):
ui.icon('search', color='primary')
def handle_filter(e: events.GenericEventArguments):
if not e.args or not isinstance(e.args, str):
return
search_term = e.args.lower().strip()
if not search_term:
return
filtered = {opt for opt in options if search_term in opt.lower()}
if len(filtered) <= MAX_OPTIONS_TO_SHOW:
new_options = set(self.state.queries).union(filtered)
search_bar.set_options(sorted(new_options))
search_bar.run_method("validate")
# Fixes errors e.g. filtering for "aaa1" when "AAA1" is in options
# will show "aaa1" and "AAA1" in the dropdown and not select any of them
# pressing Enter caused an error
search_bar.run_method("setOptionIndex", 0)
search_bar.on('input-value', handle_filter)
# Cosmetic
def handle_change(e: events.GenericEventArguments):
search_bar.set_options(self.state.queries)
search_bar.run_method("updateInputValue", "") # Clear input field
# While the prop clearable changes self.state.queries, it doesnt instantly change the dropdown options.
# Similarly removing a chip triggers 'update:model-value' which also needs to reset the options.
search_bar.on('clear', handle_change)
search_bar.on('update:model-value', handle_change) # Removing a chip triggers this event.
return search_bar
@ui.page('/')
def page():
search = SearchUI()
n = 5
options = [
f"{''.join(letters)}{i}"
for letters in itertools.product(string.ascii_uppercase, repeat=3)
for i in range(1, n+1)
]
ui.label(f"{len(options)=}")
search.add_search_bar(options=options)
ui.run(reload=True) |
Beta Was this translation helpful? Give feedback.
Thank you @BaccanoMob, that was a useful hint. I have not touched events and methods of Quasar objects yet. It was a nice opportunity to learn. Overall, this is pretty much what I wanted (plus extra stuff like triggering validation again and fixing other surprising errors that occured):