Hi all!
I’m not sure whether this should be a bug report, or a feature request, so any advice would be appreciated. I have a situation where I have a list of items for selection by users that is subject to being misspelled or misrepresented in various ways. In testing, we’ve found that rather than using an exact match (or even close match letter-for-letter) that a fuzzy match algorithm works much better. In our case, we would like to implement a Levenshtein algorithm implemented in the fuzzywuzzy python library. Another option would be something like Soundex, which would match things like “Jeff” and “Geoff”.
When browsing the documentation for dcc.Dropdown I determined that I should be able to set options returned using a callback function. All good so far. I implemented my alternative fuzzywuzzy algorithm and provided the top 5 matches to what the user typed in, as the returned, updated options list. Unfortunately, the dropdown list apparently has an additional step outside of my control (unless I’ve missed a parameter that allows for this) that filter out based (apparently) on an “in - list”, and if an option I return does not contain the string (partial or full) that the user enters, then the option is filtered out of the options list. So, “Jeff” will never return “Geoff” as an option.
Here’s a simple example of the problem I’m seeing:
from dash import Dash, dcc, html, dcc, Input, Output, State, MATCH, ALL
from dash.exceptions import PreventUpdate
import pandas as pd
from fuzzywuzzy import process
app = Dash(__name__, suppress_callback_exceptions=True)
options = [
{"label": 'Felicity Davies', "value": 'Felicity Davies'},
{"label": 'Emma Cornish', "value": 'Emma Cornish'},
{"label": 'Faith Davidson', "value": 'Faith Davidson'},
{"label": 'Emily Coleman', "value": 'Emily Coleman'},
{"label": 'Ella Clarkson', "value": 'Ella Clarkson'},
{"label": 'Elizabeth Clark', "value": 'Elizabeth Clark'},
{"label": 'Dorothy Churchill', "value": 'Dorothy Churchill'},
{"label": 'Donna Chapman', "value": 'Donna Chapman'},
{"label": 'Diane Carr', "value": 'Diane Carr'},
{"label": 'Harry Campbell', "value": 'Harry Campbell'},
{"label": 'Gordon Cameron', "value": 'Gordon Cameron'},
{"label": 'Gavin Butler', "value": 'Gavin Butler'},
{"label": 'Frank Burgess', "value": 'Frank Burgess'},
{"label": 'Evan Buckland', "value": 'Evan Buckland'},
{"label": 'Eric Brown', "value": 'Eric Brown'},
{"label": 'Edward Bower', "value": 'Edward Bower'},
]
options_df = pd.DataFrame(options)
app.layout = html.Div([
html.Button("Add Filter", id="add-filter", n_clicks=0),
html.Div(id='dropdown-container', children=[]),
html.Div(id='dropdown-container-output')
])
app = Dash(__name__)
app.layout = html.Div([
html.Div('List of All Options:', style={'color': 'blue', 'font-weight': 'bold'}),
html.Div(id='optionslist', style={'whiteSpace': 'pre-line'}, children=", ".join(options_df['value'])),
html.Br(),
html.Br(),
html.Div('Options that should be available', style={'color': 'blue', 'font-weight': 'bold'}),
html.Div(id='optionstext', style={'whiteSpace': 'pre-line'}, children=", ".join(options_df['value'])),
html.Hr(),
html.Div([
"Select all Products/Technologies Involved in the Discussion:",
dcc.Dropdown(id="my-multi-dynamic-dropdown", multi=True),
]),
])
@app.callback(
Output("my-multi-dynamic-dropdown", "options"),
Output('optionstext', 'children'),
Input("my-multi-dynamic-dropdown", "search_value"),
State("my-multi-dynamic-dropdown", "value")
)
def update_multi_options(search_value, value):
if not search_value:
raise PreventUpdate
# Make sure that the set values are in the option list, else they will disappear
# from the shown select list, but still part of the `value`.
# print(f'''options labels: {options_df['label']}''')
fuzzy_out = process.extract(search_value, choices=options_df['label'], limit=5)
fuzz_list = []
for fuzz_ret in fuzzy_out:
fuzz_list.append(fuzz_ret[0])
print(f'''Fuzz List:{fuzz_list}''')
for o in options:
print(f'''Options labels:{o["label"]}''')
if o["label"] in fuzz_list:
print(f'YES! {o["label"]} is in the fuzz_list!')
items = [o for o in options if o['label'] in fuzz_list or o["value"] in (value or [])]
print(f'Items selected: {items}')
outlist = ", ".join(fuzz_list)
return items, outlist
if __name__ == "__main__":
app.run_server(debug=True)
And here is what the app looks like when initially running:
As you enter names, you will see the “Options that should be available” change as you type. You’ll also see what the dcc.Dropdown provides as options. To see the problem, try typing “Ivan”. You’ll note that in the “Options that should be available” “Evan Buckland” is the second option (which is what I want)…but it does not show up in the options list for selection!
I don’t know whether this was intentional, and I’m asking for feature to be added, or whether it was simply an oversight. I’m concerned this additional filtering is going on at the React level, and may be more difficult to resolve. But the documentation implies that a set of options can be manipulated and returned within the callback for dcc.Dropdown, and this is misleading at best.
If I’ve missed something and I’m doing this incorrectly, please let me know! Otherwise, I’m happy to log a bug and would appreciate suggestions on whether to classify as a Bug or a Feature request. Thanks!