Hi there,
What’s the longest callback you’ve ever written? Has there ever been a long list of things that took over your editor? Like this.
@app.callback(Output('output-0', 'children'),
[Input('input-0', 'value'),
Input('input-1', 'value'),
Input('input-2', 'value'),
Input('input-3', 'value'),
Input('input-4', 'value'),
Input('input-5', 'value'),
Input('input-6', 'value'),
Input('input-7', 'value'),
Input('input-8', 'value'),
Input('input-9', 'value')])
def out(value_0, value_1, value_2,
value_3, value_4, value_5,
value_6, value_7, value_8,
value_9):
return str([value_0, value_1, value_2,
value_3, value_4, value_5,
value_6, value_7, value_8,
value_9])
Let’s simplify it.
You know, writing python, we usually use list comprehensions to shorten code. So for that bunch of Input
, we can do this.
@app.callback(Output('output-0', 'children'),
[Input(f'input-{i}', 'value') for i in range(10)])
Emm, what about variables?
Since up to the current version of the Dash decorator, the callback is passed in as a list. So we are going to use the unpacking operator.
def out(*values):
print(values[0])
results = do_something(*values)
return results
@app.callback(Output('output-0', 'children'),
[Input(f'input-{i}', 'value') for i in range(10)])
def out(*values):
print(values[0])
results = do_something(*values)
return results
Only a few lines left, right?
And what if I have some associated components, or want to generate some components dynamically? How can I add them to the trigger list in an elegant way?
Of course, it is the Pattern-Matching Callbacks that has been supported since version 1.11.
Your callbacks will look like this.
@app.callback(
Output({'type': 'dynamic-output', 'index': MATCH}, 'children'),
Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value'),
State({'type': 'dynamic-dropdown', 'index': MATCH}, 'id'),
)
def display_output(value, id):
return html.Div('Dropdown {} = {}'.format(id['index'], value))
Here, I’m going to use @robert_oc 's post as an example to write something.
If I have a questionnaire like this,
{
1: {
'type': 'choice',
'question':
'Does your agency use an electronic accounting software system (as opposed to manual)?',
'options': ['Yes', 'No', 'Skip']
},
2: {
'type': 'choice+blank',
'question':
'Has your agency recently implemented any new or substantially changed systems, for example, financial management or accounting systems? (If yes, please explain.)',
'options': ['Yes', 'No', 'Skip']
},
......
I would use a list comprehension to iterate over the list of questions and give the components some associated IDs. Then, the code will look like this.
from dash import Dash, html, dcc, Input, Output, State, MATCH, ALL
app = Dash(__name__)
questionnaire = {
1: {
'type': 'choice',
'question':
'Does your agency use an electronic accounting software system (as opposed to manual)?',
'options': ['Yes', 'No', 'Skip']
},
2: {
'type': 'choice+blank',
'question':
'Has your agency recently implemented any new or substantially changed systems, for example, financial management or accounting systems? (If yes, please explain.)',
'options': ['Yes', 'No', 'Skip']
},
3: {
'type': 'choice',
'question':
'Does your agency have a written Accounting and Financial Reporting policy?',
'options': ['Yes', 'No', 'Skip']
},
4: {
'type': 'choice',
'question':
'Does your agency have a written Personnel policy (to include travel reimbursement, fringe benefits, etc.)?',
'options': ['Yes', 'No', 'Skip']
},
5: {
'type':
'multi-choice',
'question':
'Which of the following aspects of the OJT training program were explained to you?',
'options': [
'Training Hours', 'Type of Training', 'Training Wages',
'Job Choices', 'Entry Wages'
]
},
6: {
'type':
'multi-choice',
'question':
'How did you learn about this OJT program?',
'options': [
'Contractor', 'Community Based Organization',
'Union Apprenticeship Program', 'Other'
]
},
7: {
'type':
'blank',
'question':
'What are your performance based strengths (speed, strength, power, agility, balance, conditioning, etc.)?'
},
8: {
'type': 'essay',
'question': 'Do you have any questions, comments or concerns?'
}
}
def generate(k, v):
match v['type']:
case 'choice':
return html.Div([html.P(str(k)+'. '+v['question']), dcc.RadioItems(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':False}, options={i: i for i in v['options']})])
case 'multi-choice':
return html.Div([html.P(str(k)+'. '+v['question']), dcc.Checklist(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':False}, options={i: i for i in v['options']})])
case 'choice+blank':
return html.Div([html.P(str(k)+'. '+v['question']), dcc.RadioItems(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':False}, options={i: i for i in v['options']}), dcc.Input(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':True}, disabled=True)])
case 'blank':
return html.Div([html.P(str(k)+'. '+v['question']), dcc.Input(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':False})])
case 'essay':
return html.Div([html.P(str(k)+'. ' + v['question']), dcc.Textarea(id={'index': k, 'type': v['type'], 'category':'questionnaire', 'additional':False})])
case _:
return html.Div('Something wrong...')
app.layout = html.Div([generate(k, v) for k, v in questionnaire.items()] +
[html.Br(), btn := html.Button('Submit'), answers := html.Div()])
@app.callback(
Output(
{
'category': 'questionnaire',
'type': 'choice+blank',
'additional': True,
'index': MATCH
}, 'disabled'),
Input(
{
'category': 'questionnaire',
'type': 'choice+blank',
'additional': False,
'index': MATCH
}, 'value'))
def disabled_input(v):
return False if v == 'Yes' else True
@app.callback(
Output(
btn, 'disabled'),
Input(
{
'category': 'questionnaire',
'type': ALL,
'additional': False,
'index': ALL
}, 'value'))
def disabled_btn(answer):
return False if all(answer) else True
@app.callback(Output(answers, 'children'), Input(btn, 'n_clicks'), [
State(
{
'category': 'questionnaire',
'type': ALL,
'additional': ALL,
'index': ALL
}, 'id'),
State(
{
'category': 'questionnaire',
'type': ALL,
'additional': ALL,
'index': ALL
}, 'value')
], prevent_initial_call=True)
def collect(n_clicks, index, answer):
return str([v | {'answer': answer[i]} for i, v in enumerate(index)])
if __name__ == "__main__":
app.run_server(debug=True)
Hope this helps you. XD