Black Lives Matter. Please consider donating to Black Girls Code today.

Dash: Input list option: updating the datalist from external api

I am trying to provide suggested values to a dash input (dcc.Input) from an external api. I expected that I could do this via a callback to a html.Datalist element that is provided to the dcc.Input element via the list argument. This all seems to work: the list is properly updated with html.Options obtained from the api, and you can get the suggestions to appear. However, the newly modified suggested options do not appear unless the user modifies the input… at which point it has been changed from the one used to source the suggestions.

Is there any way to make suggestions appear as soon as the datalist element is changed, and not require the user to change the input?

It turns out this is due to having a button element submit values for the dcc.Input. Since clicking on the button shifts the focus to the button, the suggestions from the Datalist are not used.

If I can just get the focus to shift back to the input on the button click, that would probably solve the problem. I don’t know much in the way of javascript, but poking around the internet led me to this attempt at a solution:

$("#submit-button").on("click", function () {
    $("#book-input").focus();
 });

I have confirmed that this actually works in a minimal html document, but it doesn’t seem to work when I try to implement it in my dash app via

jquery_url = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"
focus_input_url = "https://codepen.io/TCProctor/pen/wjgpRZ.js"
app.scripts.append_script({'external_url': jquery_url})
app.scripts.append_script({'external_url': focus_input_url})

Any suggestions would be much appreciated.

Here’s a minimal dashboard that demonstrates the javascript solution I tried above not working in dash:

import dash
from dash.dependencies import Output, Input, State
import dash_core_components as dcc
import dash_html_components as html


app = dash.Dash()

items = [dcc.Input(id='book-input',
                   list='suggested-datalist',
                   type='text'),
         html.Button(id='submit-button',
                     n_clicks=0,
                     children='btn',
                     className='button-primary'),
         html.Datalist(id='suggested-datalist')]
jquery_url = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"
focus_input_url = "https://codepen.io/TCProctor/pen/wjgpRZ.js"
focus_input_css = "https://codepen.io/TCProctor/pen/wjgpRZ.css"
app.scripts.append_script({'external_url': jquery_url})
app.scripts.append_script({'external_url': focus_input_url})
app.css.append_css({"external_url": focus_input_css})
app.layout = html.Div(items, id='parent-div')


@app.callback(Output(component_id='suggested-datalist',
                     component_property='children'),
              [Input(component_id='submit-button',
                     component_property='n_clicks')],
              [State(component_id='book-input',
                     component_property='value')])
def update_datalist(n_clicks, value):
    return [html.Option(value=value)] * 3


if __name__ == "__main__":
    app.run_server(debug=True, port=8051)

Since this works in a vanilla html document but not dash, I suspect there’s some conflict that dash is causing.

I found out that the focus isn’t the only problem. I made it so that the datalist updates in response to changes in the input itself, and ignores the button. This will result in spamming the api I’m using, but it’s the best path I see:

import dash
from dash.dependencies import Output, Input, State
import dash_core_components as dcc
import dash_html_components as html


app = dash.Dash()

items = [dcc.Input(id='book-input',
                   list='suggested-datalist',
                   type='text'),
         html.Button(id='submit-button',
                     n_clicks=0,
                     children='btn',
                     className='button-primary'),
         html.Datalist(id='suggested-datalist'),
         html.P(id='text-message')]
datalist_polyfill_url = "https://codepen.io/TCProctor/pen/PepWYP.js"
app.css.append_css({"external_url": datalist_polyfill_url})
app.layout = html.Div(items, id='parent-div')


@app.callback(Output(component_id='suggested-datalist',
                     component_property='children'),
              [Input(component_id='book-input',
                     component_property='value')])
def update_datalist(value):
    return [html.Option(value=value + 'spam')] * 3


if __name__ == "__main__":
    app.run_server(debug=True, port=8051)

In chrome (Version 65), this actually works great, but in Firefox (59), the datalist oddly only shows if you delete characters. This isn’t just a Firefox problem, as it works just fine with the dynamic datalist updates using javascript that I’ve found.

Note: datalist_polyfill_url is a url for the datalist-polyfill npm package, a javascript library that adds datalist support to otherwise unsupported browsers (Safari, IE).