Are you open to turning it into a clientside callback?
i was playing around with that option too, but i saw the triggered data on dash_clientside is less than what is in server callback. What are you thinking that will help in clientside?
Yup! Global variables.
app.clientside_callback(
"""
function (clicks, state) {
if (window.num) {
if (window.num == clicks.length) {
button = JSON.parse(String(dash_clientside.callback_context.triggered.map(t => t.prop_id)).split('.')[0])
disp = []
for (y=0; y<window.num; y++) {
if (y == button.index) {
disp.push({'display':'inline-block'})
} else {
disp.push({'display':'none'})
}
}
s = JSON.parse(JSON.stringify(state))
s['1'] = !s['1']
return [s, disp]
}
}
window.num = clicks.length
return window.dash_clientside.no_update, [window.dash_clientside.no_update]*window.num
}
""",
Output("accordion", "state"),
Output({"type": "text-label", "index": ALL}, "style"),
Input({"type": "btn-click", "index": ALL}, "n_clicks"),
State("accordion", "state")
)
You may be able to declare the variable in a regular javascript outside and then access it inside this function.
This feels quite hacky, client side callbacks often feel a lot more complex and convoluted
I’ll play around with both options and get something working,
Thanks again
Another hacky way would be to reset the n_clicks every valid response, where a valid request is where the clicks[triggered_id.index] > 0. Else return no update.
This would trigger twice every click. Lol.
I had another idea by using the store to save the action done, and reset it after processing, but seems like there is a limitation with doing this using dash-extensions. The client side function setting value to 10 never works.
@Emil any ideas here?
import flask
from dash import State, ALL, dcc, no_update, html
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform
f_app = flask.Flask(__name__)
app = DashProxy(transforms=[MultiplexerTransform()], server=f_app)
app.layout = html.Div(children=[
html.Button("Click", id="btn", n_clicks=0),
html.Div([], id="btn-box", style={"display": "flex", "gap": "5px"}),
html.Div("None", id="txt"),
dcc.Store("store", data=0, storage_type="local"),
])
@app.callback(
Output("btn-box", "children"),
Output("store", "data"),
Input("btn", "n_clicks"),
State("btn-box", "children"),
prevent_initial_call=True
)
def add_button(id, children):
children.append(html.Button(f"Button {id}", id={"type": "btn-group", "index": id}))
print(id)
return children, id # comment this row out to see the clientside callback update the value correctly
return children, no_update
app.clientside_callback(
"""
function(click, data) {
console.log(data)
return [10, `Clicked ${data}`]
}
""",
Output("store", "data"),
Output("txt", "children"),
Input({"type": "btn-group", "index": ALL}, "n_clicks"),
State("store", "data"),
prevent_initial_call=True
)
if __name__ == '__main__':
app.run_server(debug=True)
When you say limitation, how exactly do you mean?
if you run the code, you will see that the update in the client side callback does not happen. the data value of store never gets set to the value 10
Does the console show info or errors?
no, and from this server side only test, you can see in the server prints that the function run correctly, but somehow the Multiplexer decides to reverse the order of results
import flask
from dash import State, ALL, dcc, no_update, html
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform
f_app = flask.Flask(__name__)
app = DashProxy(transforms=[MultiplexerTransform()], server=f_app)
app.layout = html.Div(children=[
html.Button("Click", id="btn", n_clicks=0),
html.Div([], id="btn-box", style={"display": "flex", "gap": "5px"}),
html.Div("None", id="txt"),
dcc.Store("store", data=0, storage_type="local", clear_data=True),
])
@app.callback(
Output("btn-box", "children"),
Output("store", "data"),
Input("btn", "n_clicks"),
State("btn-box", "children"),
prevent_initial_call=True
)
def add_button(id, children):
children.append(html.Button(f"Button {id}", id={"type": "btn-group", "index": id}, n_clicks=0))
print(f"add button {id}")
return children, id
@app.callback(
Output("store", "data"),
Output("txt", "children"),
Input({"type": "btn-group", "index": ALL}, "n_clicks"),
State("store", "data"),
prevent_initial_call=True
)
def func(click, data):
print(f"func {data}")
return 85, f"Last clicked id = {data}"
if __name__ == '__main__':
app.run_server(debug=True)
Question, on the clientside callback, when testing. Did you reload the page? Or just use the hot reloading?
full reload, but the same occurs with regular server side callbacks too
What happens if you put your outputs into a list in the callback.
Like callback([Outputs])
no, also tried various other ways like this
@app.callback(
Output("store", "data"),
Output("txt", "children"),
inputs=dict(
a=Input({"type": "btn-group", "index": ALL}, "n_clicks"),
b=State("store", "data")),
prevent_initial_call=True
)
@Emil this seems to be something connected to the MultiPlexer
I am not sure that I understand what the issue is. Could you post a MWE that demonstrates the problem?
somehow the Multiplexer decides to reverse the order of results
import flask
from dash import State, ALL, dcc, no_update, html
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform
f_app = flask.Flask(__name__)
app = DashProxy(transforms=[MultiplexerTransform()], server=f_app)
app.layout = html.Div(children=[
html.Button("Click", id="btn", n_clicks=0),
html.Div([], id="btn-box", style={"display": "flex", "gap": "5px"}),
html.Div("None", id="txt"),
dcc.Store("store", data=0, storage_type="local", clear_data=True),
])
@app.callback(
Output("btn-box", "children"),
Output("store", "data"),
Input("btn", "n_clicks"),
State("btn-box", "children"),
prevent_initial_call=True
)
def add_button(id, children):
children.append(html.Button(f"Button {id}", id={"type": "btn-group", "index": id}, n_clicks=0))
print(f"add button {id}")
return children, id
@app.callback(
Output("store", "data"),
Output("txt", "children"),
Input({"type": "btn-group", "index": ALL}, "n_clicks"),
State("store", "data"),
prevent_initial_call=True
)
def func(click, data):
print(f"func {data}")
return 85, f"Last clicked id = {data}"
if __name__ == '__main__':
app.run_server(debug=True)
@Emil did you get a chance to look at the working example above
@dales Sorry, I don’t see the problem. What is reversed?
the function def func
should set 85 to the store, but this is not happening
you can see from the prints that the server does receive them both. But they go to the Multiplexer and somehow I never see 85 appearing on screen