Hello!
I’m trying to create a user walk through type app with Dash. I’m using the Dash Mantine Stepper component to walk the user through a few steps.
How can I prevent the user from advancing to the next step until the current (and all prior steps) are complete? In this example app, the user first needs to upload a csv or excel file. They should not be able to continue until they have uploaded a valid file. The task they will need to complete at each step will of course be different though.
Here’s some example code:
import dash_mantine_components as dmc
from dash import Dash, dcc, html, dash_table, Input, Output, State, callback, ctx
import base64
import datetime
import io
import pandas as pd
min_step = 0
max_step = 3
active = 0
app = Dash(
__name__,
external_stylesheets=[
# include google fonts
"https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;900&display=swap"
],
)
app.layout = html.Div(
[
dmc.Stepper(
id="stepper-basic-usage",
active=active,
breakpoint="sm",
children=[
dmc.StepperStep(
label="First step",
description="Upload a file",
children=html.Div([
dmc.Text("Step 1: Upload file", align="center"),
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '60%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin-left': '20%',
'margin-right': '20%'
},
# Allow multiple files to be uploaded
multiple=True
),
html.Div(id='output-data-upload'),
]),
),
dmc.StepperStep(
label="Second step",
description="Verify email",
children=dmc.Text("Step 2 content: Verify email", align="center"),
),
dmc.StepperStep(
label="Final step",
description="Get full access",
children=dmc.Text(
"Step 3 content: Get full access", align="center"
),
),
dmc.StepperCompleted(
children=dmc.Text(
"Completed, click back button to get to previous step",
align="center",
)
),
],
),
dmc.Group(
position="center",
mt="xl",
children=[
dmc.Button("Back", id="back-basic-usage", variant="default"),
dmc.Button("Next step", id="next-basic-usage"),
],
),
]
)
def parse_contents(contents, filename, date):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return html.Div([
html.H5(filename),
html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
df.to_dict('records'),
[{'name': i, 'id': i} for i in df.columns]
),
html.Hr(), # horizontal line
# For debugging, display the raw contents provided by the web browser
html.Div('Raw Content'),
html.Pre(contents[0:200] + '...', style={
'whiteSpace': 'pre-wrap',
'wordBreak': 'break-all'
})
])
@callback(
Output("stepper-basic-usage", "active"),
Input("back-basic-usage", "n_clicks"),
Input("next-basic-usage", "n_clicks"),
State("stepper-basic-usage", "active"),
prevent_initial_call=True,
)
def update(back, next_, current):
button_id = ctx.triggered_id
step = current if current is not None else active
if button_id == "back-basic-usage":
step = step - 1 if step > min_step else step
else:
step = step + 1 if step < max_step else step
return step
@callback(
Output('output-data-upload', 'children'),
Input('upload-data', 'contents'),
State('upload-data', 'filename'),
State('upload-data', 'last_modified'),
prevent_initial_call=True,
)
def update_output(list_of_contents, list_of_names, list_of_dates):
if list_of_contents is not None:
children = [
parse_contents(c, n, d) for c, n, d in
zip(list_of_contents, list_of_names, list_of_dates)]
return children
if __name__ == '__main__':
app.run(debug=True)
Thank you!