Direct input-focus to a specific text-input widget?

I’d like to return the focus for text input back to a text-input widget after some other activities. Currently, the user types their text, clicks/tabs away, the action is triggered, the text-input is cleared, but the user has to click on the text-input widget again before entering more text.

How can I direct the input-focus back to the widget?

Hi @matthiasautrata

That is a good question. I tried to find a solution using the built in TextInput, but could not. I’ve asked for some documentation to be added to show how to solve your question. See Document how to select a specific html element of a Panel component · Issue #5372 · holoviz/panel (github.com).

I had a requirement myself for something similar to what you need so I ended up making this ReactiveHTML component which does the job:

import panel as pn
import param

class FocusedInput(pn.reactive.ReactiveHTML):
    value = param.String()
    focus = param.Boolean(default=True)

    _template = """
    <input id="entry" autofocus
    onchange="${script('syncclear')}"
    onfocusout="${script('focus')}"></input>
    <input id="focus" hidden value="${focus}"></input>
    """

    _scripts = {
        "syncclear": "data.value=entry.value; entry.value='';",
        "focus": """if(data.focus) {
                        setTimeout(function () {
                        entry.focus();
                        }, 0);
                    }
        """
    }

    _dom_events = {'entry': ['change']}

    def __init__(self, **params):
        super().__init__(**params)
        # Firefox ignores autofocus attribute...
        def onload(self):
            self.focus = False
            self.focus = True
        from functools import partial
        pn.state.onload(partial(onload, self))

    def _entry_change(self, d):
        print(d.model.data.value)

def processing(event):
    if editable.focus:
        editable.focus = False
        button.name = 'Unfocused'
    else:
        editable.focus = True
        button.name = 'Focused'


editable = FocusedInput()
button = pn.widgets.Button(name='Focused')
col = pn.Column(editable, editable.param.value, button, margin=50)
button.on_click(processing)
col.servable()

The setTimeout and hacky stuff for pageload is because Firefox does not behave properly with autofocus attribute. If you prefer to just target Chrome, you can call “entry.focus()” directly instead of in a Timeout, and you can remove all the init code after the Firefox comment.

I’m sure there’s a way to do this with a TextInput widget by wrapping it into a ReactiveHTML template then traversing down the child nodes within the JS callbacks. I didn’t need the extra widget features for this however so I went the simpler route.

2 Likes