That’s to do with how Dash works rather than specific to dash-bootstrap-components
. If you want the table to persist after being uploaded, you’ll have to cache the data somehow, then get Dash to retrieve it every time you visit Page 1 again.
Here’s an example using dcc.Store
. I did this pretty quickly, so there might be a better way to set it up, but it works:
Modify app.layout
to
app.layout = html.Div(
[
dcc.Location(id="url", pathname="/page-1"),
dcc.Store(id="upload-storage"),
navbar,
dbc.Container(id="content", style={"padding": "20px"}),
]
)
When a file is uploaded, we’ll serialise it and save it to upload-storage
. Modify parse_contents
to the following:
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 {
"filename": filename,
"df": df.to_dict(orient="rows"),
"date": date,
}
Then create a callback to populate upload-storage
when a file or files are uploaded:
@app.callback(
Output("upload-storage", "data"),
[Input("upload-data", "contents")],
[State("upload-data", "filename"), State("upload-data", "last_modified")],
)
def store_upload(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
Finally we want to use data from upload-storage
when the page is loaded. Let’s first make a function that generates page 1 from the data instead of having it defined as a global variable, so replace page1 = ...
with
def make_page1(data):
if data is not None:
uploaded_data = []
for d in data:
filename = d["filename"]
df = pd.DataFrame(d["df"])
date = d["date"]
uploaded_data.append(
html.Div(
[
html.H5(filename),
html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
data=df.to_dict("rows"),
columns=[{"name": i, "id": i} for i in df.columns],
),
]
)
)
else:
uploaded_data = None
return html.Div(
[
dcc.Upload(
id="upload-data",
children=html.Div(
["Drag and Drop or ", html.A("Select Files")]
),
style={
"width": "100%",
"height": "60px",
"lineHeight": "60px",
"borderWidth": "1px",
"borderStyle": "dashed",
"borderRadius": "5px",
"textAlign": "center",
"margin": "10px",
},
# Allow multiple files to be uploaded
multiple=True,
),
html.Div(uploaded_data, id="output-data-upload"),
]
)
Finally we want this to get called when the pathname changes, so change the url callback to
@app.callback(
Output("content", "children"),
[Input("url", "pathname"), Input("upload-storage", "data")],
)
def display_page(pathname, data):
if pathname == "/page-1":
return make_page1(data)
if pathname == "/page-2":
return page2
# if not recognised, return 404 message
return html.P("404 - page not found")
Hope that all makes sense. Let me know if you can’t get it to work.