Skip to content

Keypress callback #723

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

Closed
mflevine opened this issue May 16, 2019 · 15 comments
Closed

Keypress callback #723

mflevine opened this issue May 16, 2019 · 15 comments

Comments

@mflevine
Copy link

I would like to be able to capture keypresses and use them for callbacks. This could be used to make keyboard shortcuts in place of buttons.

@Jaudouard
Copy link

Jaudouard commented May 18, 2019

Perhaps a hidden input field with autofocus on ?
You would have several callbacks listening to this input and executing code accordingly.

I guess you would have to add an extra callback dedicated to clearing the input field everytime it is filled with something as well.

@mflevine
Copy link
Author

Seems pretty hacky this way. Wont the input field lose focus as soon as the user starts interacting with other inputs?

@Jaudouard
Copy link

You are right, pretty hacky...

@konstantinmiller
Copy link

Oh, I'm a bit surprised. Is there no way one can create callbacks for keyboard events? That seems like a basic interaction pattern.

@emilhe
Copy link
Contributor

emilhe commented Jul 31, 2020

You could try the Keyboard component in dash-extensions,

import dash
import dash_html_components as html
import json

from dash.dependencies import Output, Input
from dash_extensions import Keyboard

app = dash.Dash()
app.layout = html.Div([Keyboard(id="keyboard"), html.Div(id="output")])


@app.callback(Output("output", "children"), [Input("keyboard", "keydown")])
def keydown(event):
    return json.dumps(event)


if __name__ == '__main__':
    app.run_server()

https://pypi.org/project/dash-extensions/

@alexcjohnson
Copy link
Collaborator

@emilhe Thanks for pointing out dash-extensions - lots of good stuff there! Some overlap with functionality we're already working on (eg flattened callback args #1180) and other things we haven't started on but have discussed. In particular Trigger #1054 is something we're very interested in adding to Dash itself, and Keyboard would be a great addition to dash-core-components. You're of course welcome to continue developing these features separately under dash-extensions but if you're interested to PR the more generally-applicable features into Dash or dash-core-components directly, we'd be glad to help 😎

@emilhe
Copy link
Contributor

emilhe commented Aug 3, 2020

@alexcjohnson Thanks! As the components mature, i think it would be good (in particular for new users), if they were moved to dash-core-components. The Trigger component would probably be a good place to start (it is pretty simple), but i feel (based on community feedback) that the functionality provided by the Download and ServersideOutput components are even more needed. When i find some time, i'll take a look at the contribution guidelines :)

@aiqc
Copy link

aiqc commented Aug 2, 2022

Hmm looks like the Keyboard class got dropped from dash_extensions.
Not seeing it in their docs

>>> from dash_extensions import Keyboard

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'Keyboard' from 'dash_extensions' (/Users/layne/.pyenv/versions/aiqc_dev/lib/python3.7/site-packages/dash_extensions/__init__.py)

@aiqc
Copy link

aiqc commented Aug 2, 2022

@emilhe
Copy link
Contributor

emilhe commented Aug 3, 2022

The Keyboard component was removed in favor of the more general EventListener component. While the latter does have feature parity (and beyond), the syntax is a bit more convoluted, so I am starting to rethink if I made the right decision.

https://www.dash-extensions.com/pages/components/event-listener

@phsamuel
Copy link

Feeling the same. Capturing the keyboard event should have been much easier. I am writing a local application with dash because of the good support of Cytoscape. But maybe it would have been much easier if I simply used QT.

@S0mbre
Copy link

S0mbre commented Oct 19, 2022

I've succeeded in applying the EventListener from dash extensions. For those who'd need, here's an example code I use to capture Return key hits on a couple of Mantine Inputs:

from dash import dcc, html, callback, Input, Output, State, ctx, no_update
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
from dash_iconify import DashIconify
from dash_extensions import EventListener

# CAPTURE ONLY KEY PRESSES
events = [{'event': 'keydown', 'props': ['key']}]

# THE INPUTS
input_login = EventListener(dmc.TextInput(label='', id='login__input_login', placeholder='Username', radius='lg', icon=[DashIconify(icon='bxs:user', width=20)],
                            size='md', style={'margin': '0% 1%'}), id='login__input_login_events', events=events)
input_password = EventListener(dmc.PasswordInput(label='', id='login__input_password', placeholder='Password', radius='lg', icon=[DashIconify(icon='bxs:lock-alt', width=20)],
                               size='md', style={'margin': '0% 1%'}), id='login__input_password_events', events=events) 

#======================== MAIN LAYOUT ========================#

def layout():
    # there are also other components here, but I've cut them away for clarity...
    return html.Div([dmc.LoadingOverlay(dmc.Stack([input_login, input_password], 
                                                  justify='center', align='stretch', spacing='lg'))], 
                                                  style={'margin': '10% 30%'})

#======================== CALLBACKS ========================#

@callback(
    output={
        'hi_user_value': Output('login__hi_user', 'children'),
        'alert_err_value': Output('login__alert_error', 'children'),
        'alert_err_hidden': Output('login__alert_error', 'hide'),
        'div_admin_action_hidden': Output('login__div_admin_actions', 'hidden'),
        'div_logged_in_hidden': Output('login__div_logged_in', 'hidden'),
        'div_login_hidden': Output('login__div_login', 'hidden')
    },
    inputs=[
        Input('login__input_login_events', 'n_events'),           # login input keydown event count
        Input('login__input_password_events', 'n_events'),    # password input keydown event count
        State('login__input_login', 'value'),                             # login value
        State('login__input_password', 'value'),                      # password value
        State('login__input_login_events', 'event'),                # login input keydown event
        State('login__input_password_events', 'event')          # password input keydown event
    ]
)
def update_layout(login_nevents, password_nevents, in_login, in_password, login_event, password_event): 
    # get trigger source, return if none  
    fired_id = ctx.triggered_id
    if not fired_id:
        raise PreventUpdate

    # block updating if any other key was hit BUT the Enter key on either of the two inputs
    if (fired_id == 'login__input_password_events' and (not password_nevents or password_event.get('key', '') != 'Enter')) or \
       (fired_id == 'login__input_login_events' and (not login_nevents or login_event.get('key', '') != 'Enter')):
        raise PreventUpdate
    
    # Enter was pressed on login input
    login_return = fired_id == 'login__input_login_events' and login_nevents
    # Enter was pressed on password input
    passw_return = fired_id == 'login__input_password_events' and password_nevents

    if login_return or passw_return:
        # do login stuff ...

    raise PreventUpdate

BTW there's no need to wrap your dash app in dash_extensions.enrich.DashProxy (as suggested in the docs example). Mine works like a charm on a bare dash app.

@ndrezn
Copy link
Member

ndrezn commented Mar 20, 2024

@hannahker, how did PS do this for the LBL project?

@hannahker
Copy link

The dash-extensions EventListener works like a charm for this! Similar to @S0mbre, you define as :

EventListener(
      events=[
          {
              "event": "keydown",
              "props": ["key", "ctrlKey"],
          }
      ],
      id="keybind-event-listener",
  )

Then you can attach callbacks using Input("keybind-event-listener", "event")

@gvwilson
Copy link
Contributor

Hi - we are tidying up stale issues and PRs in Plotly's public repositories so that we can focus on things that are most important to our community. If this issue is still a concern, please add a comment letting us know what recent version of our software you've checked it with so that I can reopen it and add it to our backlog. (Please note that we will give priority to reports that include a short reproducible example.) If you'd like to submit a PR, we'd be happy to prioritize a review, and if it's a request for tech support, please post in our community forum. Thank you - @gvwilson

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests