Daily Tips - If I have a Bunch of Triggers 🎈

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? :thinking:

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

:eyes:

@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

Keywords: Pattern-Matching Callbacks, Unpacking Operator, List Comprehension, Questionnaire

11 Likes

Great tips @stu! I’m really enjoying these short Dash lessons, thanks for sharing :star2:

1 Like

Very useful tip @stu

Your tips of the day are turning into an important guide of the most common and challenging questions.

Keep up the great work :muscle:

3 Likes

Hi @stu,

How about if all the information is come from a dataframe but need to create a own design table?

Able to create own designed table but don’t know how to connect to dataframe and extract the information according to each row and column name.

Any advise on this matter?

In fact, pd provides a lot of convenient APIs.
such as:

df.to_dict
df.to_json
...

and, you can do this

for idx, row in df.iterrows():
    print(row['a'])