Does what work?
The event listener in the above example is in added to the whole document, to add it to a pattern-matched element, you’d need to do something like this:
assets/js file
function stringifyId(id) {
if (typeof id !== "object") {
return id;
}
const stringifyVal = (v) => (v && v.wild) || JSON.stringify(v);
const parts = Object.keys(id)
.sort()
.map((k) => JSON.stringify(k) + ":" + stringifyVal(id[k]));
return "{" + parts.join(",") + "}";
}
app.py
import dash
from dash import Input, Output, State, ClientsideFunction, html, dcc, ctx, MATCH
import json
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div([html.Button("Undo-Button", id={'index': x, 'type':"undoButton"}),
html.Button("Redo-Button", id={'index': x, 'type':"redoButton"}),
dcc.Input(id={'index': x, 'type':'input'}),
html.Div(id={'index': x, 'type':'output'}, style={'backgroundColor': 'green', 'width': '300px', 'height': '100px', 'color': 'white'})]) for x in range(3)
])
app.clientside_callback(
"""
function(inp, undo, redo) {
document.getElementById(stringifyId(inp)).addEventListener("keydown", function(event) {
if (event.ctrlKey) {
if (event.key == 'z') {
document.getElementById(stringifyId(undo)).click()
event.stopPropagation()
}
if (event.key == 'x') {
document.getElementById(stringifyId(redo)).click()
event.stopPropagation()
}
}
});
return window.dash_clientside.no_update
}
""",
Output({'index': MATCH, 'type':"undoButton"}, "id"),
Input({'index': MATCH, 'type':"input"}, "id"),
State({'index': MATCH, 'type':"undoButton"}, "id"),
State({'index': MATCH, 'type':"redoButton"}, "id"),
)
@app.callback(
Output({'index': MATCH, 'type': "output"}, "children"),
Input({'index': MATCH, 'type': "undoButton"}, "n_clicks"),
Input({'index': MATCH, 'type': "redoButton"}, "n_clicks"),
prevent_initial_call=True
)
def show_value(n1, n2):
return json.dumps(ctx.triggered_id)
if __name__ == "__main__":
app.run_server(debug=True)
Give this a test and see how you like it, the event listener here is added to the inputs.
Here it is added to the div, note that you will need something to actually be receiving the keydown, which then bubbles up to the div:
import dash
from dash import Input, Output, State, ClientsideFunction, html, dcc, ctx, MATCH, Patch
import json
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div([html.Button("Undo-Button", id={'index': x, 'type':"undoButton"}),
html.Button("Redo-Button", id={'index': x, 'type':"redoButton"}),
html.Div(id={'index': x, 'type':'output'}, children=[dcc.Input(id={'index': x, 'type':'input'})], style={'backgroundColor': 'green', 'width': '300px', 'height': '100px', 'color': 'white'})]) for x in range(3)
])
app.clientside_callback(
"""
function(out, undo, redo) {
document.getElementById(stringifyId(out)).addEventListener("keydown", function(event) {
if (event.ctrlKey) {
if (event.key == 'z') {
document.getElementById(stringifyId(undo)).click()
event.stopPropagation()
}
if (event.key == 'x') {
document.getElementById(stringifyId(redo)).click()
event.stopPropagation()
}
}
});
return window.dash_clientside.no_update
}
""",
Output({'index': MATCH, 'type':"undoButton"}, "id"),
Input({'index': MATCH, 'type':"output"}, "id"),
State({'index': MATCH, 'type':"undoButton"}, "id"),
State({'index': MATCH, 'type':"redoButton"}, "id"),
)
@app.callback(
Output({'index': MATCH, 'type': "output"}, "children"),
Input({'index': MATCH, 'type': "undoButton"}, "n_clicks"),
Input({'index': MATCH, 'type': "redoButton"}, "n_clicks"),
prevent_initial_call=True
)
def show_value(n1, n2):
child = Patch()
child[1] = json.dumps(ctx.triggered_id)
return child
if __name__ == "__main__":
app.run_server(debug=True)