-
-
Notifications
You must be signed in to change notification settings - Fork 685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide search-as-you-type within the options of ui.select #272
Comments
Yes, QSelect offers what we are looking for: |
So far I couldn't get |
I just pushed an example of an search-as-you-type example which uses the public API of https://www.thecocktaildb.com to search for cocktails: search-cocktails_small.mp4This is a very different thing from what @falkoschindler wanted to achieve: filtering values of a |
@rodja Sure, you can update search results on every input value change. But as explained in my first comment this issue is about searching within the options of |
Lookin at the Quasar docs they provide the following.... <q-select
filled
v-model="model"
use-input
input-debounce="0"
label="Simple filter"
:options="options"
@filter="filterFn"
style="width: 250px"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select> You prob also have to pass those other props. |
I feel like I'm pretty close to figuring it out. I know how to update the options from nicegui import ui
select = ui.select(options=[1,2,3])
def change():
select.options = [4,5,6]
select.update()
button = ui.button(on_click=change)
ui.run() Maybe someone else can take a look at the code below. import re
from nicegui import ui
stringOptions = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle', "Goodyear"]
def search_strings(query, string_list):
escaped_query = re.escape(query)
pattern = re.compile(r"\b" + escaped_query + r"\w*\b", re.IGNORECASE)
matches = [string for string in string_list if pattern.search(string)]
return matches
async def handleFilter(event):
val = event['args']
val = val.lower()
print(val)
if val == '' or val == None:
print(stringOptions)
select.options = stringOptions
select.update()
else:
filtered = search_strings(val, stringOptions)
print(filtered)
select.options = filtered
select.update()
select = ui.select(options=stringOptions, label="Test").props('''
use-input
hide-selected
fill-input
input-debounce="0"
style="width: 250px; padding-bottom: 32px"
''')
select.on(type='filter', handler=handleFilter, throttle=3)
ui.run() |
@Allen-Taylor Very interesting! You found some props I wasn't aware of and that seem to be important for combining an input with a dropdown menu. I boiled it down to the following code: #!/usr/bin/env python3
import re
from nicegui import ui
names = ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle', 'Goodyear']
def on_filter(event: dict) -> None:
select.options = [n for n in names if re.search(event['args'], n, re.IGNORECASE)]
select.update()
select = ui.select(names).props('use-input hide-selected fill-input').on('filter', handler=on_filter)
ui.run() Without |
class QSelectWithFilter(jp.QSelect):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.initial_options = kwargs.get('options', [])
self.use_input = True
self.allowed_events = ['input', 'remove', 'add', 'new_value', 'filter', 'filter_abort', 'keyup', 'focus', 'blur']
self.append('keyup', self.filter_function)
def filter_function(self, msg):
self.options = list(filter(lambda x: msg.value.lower() in str(x).lower(), self.initial_options)) This is the JustPy example which should be similar. |
@Allen-Taylor Interesting! Where did you find JustPy seems to have the same problem:
|
Meanwhile I managed to get one step further: Using the def on_filter(event: dict) -> None:
select.options = [n for n in names if not event['args'] or re.search(event['args'], option, re.IGNORECASE)]
select.update()
select = ui.select(names).props('use-input hide-selected fill-input').on('input-value', handler=on_filter) |
This code lets me sort the drop down. import re
from nicegui import ui
options = ['United States',
'China',
'Japan',
'Germany',
'United Kingdom',
'France',
'Brazil',
'India',
'Italy',
'Canada']
def search_strings(query, string_list):
escaped_query = re.escape(query)
pattern = re.compile(r"\b" + escaped_query + r"\w*\b", re.IGNORECASE)
matches = [string for string in string_list if pattern.search(string)]
return matches
def handle_filter(event):
val = event['args']
if val:
filtered = search_strings(val, options)
if filtered:
select.options = filtered
else:
select.options = options
else:
select.options = options
select.update()
with ui.row().classes('q-gutter-md row'):
select = ui.select(options=options, label="Please select a country:").props(
''' use-input hide-selected fill-input stack-label ''') \
.style("width: 250px; padding-bottom: 32px")
select.on(type='input-value', handler=handle_filter)
ui.run() However, keyboard mashing on the input I get this error.
Also, with the using the up and down arrows on the keyboard messes with the filter. |
Yes, the internals of We can either make choice elements robust against arbitrary values, or implement our search-as-you-type input selection as a completely new UI element. |
You could have something like ui.select(options[...], filter=True) |
Got it working with custom vue component. main.py from select_filter import Select_Filter
from nicegui import ui
options = ['United States',
'China',
'Japan',
'Germany',
'United Kingdom',
'France',
'Brazil',
'India',
'Italy',
'Canada']
with ui.card():
qsel = Select_Filter(options=options).style("width: 250px")
ui.run() select_filter.js export default {
props: ['options'],
template: `
<q-select
:model-value="model"
use-input
hide-selected
fill-input
input-debounce="0"
:options="options"
@filter="filterFn"
@input-value="setModel"
>
<template v-slot:no-option>
<q-item>
<q-item-section>
No results
</q-item-section>
</q-item>
</template>
</q-select>
`,
setup(props) {
const stringOptions = Vue.ref(props.options)
const model = Vue.ref(null)
const options = Vue.ref(stringOptions.value)
const filterFn = (val, update, abort) => {
update(() => {
const needle = val.toLocaleLowerCase()
options.value = stringOptions.value.filter(v => v.toLocaleLowerCase().indexOf(needle) > -1)
})
}
const setModel = (val) => {
model.value = val
}
Vue.watch(() => props.options, (newVal) => {
stringOptions.value = newVal
options.value = stringOptions.value
})
return {
model,
options,
filterFn,
setModel
}
},
}; select_filter.py from typing import List
from nicegui.dependencies import register_component
from nicegui.element import Element
register_component('select_filter', __file__, 'select_filter.js')
class Select_Filter(Element):
def __init__(self, options: List[str]) -> None:
super().__init__('select_filter')
self._props['options'] = options |
Yes, a custom component is definitely an option. |
That's really difficult... I got it somehow working on the search-as-you-type branch. But the UX is pretty bad: When the user types the first letters, the list is filtered correctly. But when the user tries to navigate the list with cursor keys, the list immediately collapses to a single option. This is because the highlighting causes an In contrast, @Allen-Taylor's approach works quite nicely. It doesn't use the So maybe adding a dedicated |
Or can we let the existing |
This is starting to look very promising! I managed to wrap |
Ok, this feature is finally complete. I just merged it onto main. |
As discussed in #267 it would be nice to be able to search within the options of
ui.select
. Or maybe it's rather an auto-suggest extension ofui.input
.The text was updated successfully, but these errors were encountered: