dash-app-gallery icon indicating copy to clipboard operation
dash-app-gallery copied to clipboard

Adding Pattern Matching app

Open Coding-with-Adam opened this issue 3 years ago • 2 comments

@IcToxi proposed the following community app that has pattern matching:

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 is 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",
    ),
)(lambda v: False if v == "Yes" else True)


app.callback(
    Output(btn, "disabled"),
    Input(
        {"category": "questionnaire", "type": ALL, "additional": False, "index": ALL},
        "value",
    ),
)(lambda answer: 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)

Coding-with-Adam avatar May 07 '22 16:05 Coding-with-Adam

Hi @IcToxi
It would be great to have a pattern matching example. Thank you. This code is a lot longer than any other app in the gallery. Could we make it more beginner-friendly by shortening it; maybe 100-130 lines of code?

Also, I'm not sure why but I get an error when I try to run the code above:

    match v["type"]:
          ^
SyntaxError: invalid syntax

Coding-with-Adam avatar May 07 '22 16:05 Coding-with-Adam

Hi @Coding-with-Adam, I thought of a way to shorten it by taking out the questionnaire. That's not a bug, it's a switch-case statement that is new in 3.10.

IcToxi avatar May 07 '22 17:05 IcToxi

Closing this since it's related to the PR that was closed.

Coding-with-Adam avatar May 31 '24 18:05 Coding-with-Adam