Setting focus back to widget after python on_change callback

Hi, apologies in advance if this is documented. I was having trouble restoring focus to an input after a python on_change callback. For example, consider the case of a MultiSelect widget ; it would be useful to be able to scroll up/down inside the whether or not we’ve registered a on_change(“value”) callback. It seems natural to me that this doesn’t happen by default, since the whole document could be re-rendered, but if some small work is being done on the python side it would be nice to be able to reset the focus back to the element.

I figured I could do this in a CustomJS callback, but this seems to accept a handle to the corresponding MultiSelect model and not the MultiSelectView (I assume there’s some limitation here but I’m not familiar with the underlying machinery), from which it would be easy to interact with the DOM element. I can add a “focus” attribute to the MultiSelect model, but that seems too heavy of a solution. I can hardcode in JS which DOM element I intend to interact with:

from bokeh.layouts import column

from bokeh.models import CustomJS

from bokeh.models.widgets import Div, MultiSelect

from bokeh.plotting import curdoc

div = Div(text="-1")

sel = MultiSelect(title="Select:",

                  value=["2", "3"],

                  options=[(str(i), "option" + str(i)) for i in range(10)],

                  size=8)

def on_sel_change(attr, old_val, new_val):

  div.text = str(new_val)

sel.on_change("value", on_sel_change)

sel.callback = CustomJS(args=dict(sel=sel), code="""

    // console.log(sel);

    // Can we invoke JQuery here? Can we reach the correspoding view?

    document.getElementsByTagName("select")[0].focus();

    """)

curdoc().add_root(column(sel, div))

This does work seamlessly but it seems like there should be a better solution. Thanks, Andy

To give at least one workaround that feels less hacky, I remembered that I can set the name attribute of the HTML element, and then refer to that name in the JS callback. So as long as one makes sure to keep names unique, something like this ought to work:

from bokeh.layouts import column

from bokeh.models import CustomJS

from bokeh.models.widgets import Div, MultiSelect

from bokeh.plotting import curdoc

div = Div(text="-1")

sel = MultiSelect(title="Select:",

                  value=["2", "3"],

                  options=[(str(i), "option" + str(i)) for i in range(10)],

                  size=2,

                  name="unique_name_for_this_select",

                  ) 

def on_sel_change(attr, old_val, new_val):

  div.text = str(new_val)

sel.on_change("value", on_sel_change)

sel.callback = CustomJS(args=dict(sel=sel), code="""

    document.getElementsByName(sel.name)[0].focus();

    """)

curdoc().add_root(column(sel, div))