Need help with Modal loading

Hello All,
I am in a situation where I am seeking any possible help.

Situation : I am trying to load a page with more than 500 small html boxes (or cards in terms of Bootstrap) and wanted to show the details of each card in a Modal window when the button is clicked.
Below is the code and the snap shot of the page that is loading. The issue I am facing here is why after pressing ‘Job Detail’ button my callback function is not firing up.

I tried different ways like using single call back instead of two, or loading the Modal window in my for loop and other as well. But none worked. Sometimes got “Loading Dependencies error” or sometimes the page was not able to load.
Here is the code:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import pyodbc
import pandas as pd
import math
from app import app

I have deleted the connection string–

sql=“select * from TABLE(QSYS2.ACTIVE_JOB_INFO())”
df=pd.read_sql_query(sql,connection)
rowCount = math.ceil(df.shape[0]/4)
totalJobs = df.shape[0]

app.layout = html.Div([
html.Div([
html.H2(‘CAS System Jobs’, style={‘color’: ‘white’,‘marginLeft’: ‘3%’,‘marginTop’: ‘1%’})

],style={'background': '#139847', 'float': 'left', 'marginBottom': 10, 'width': '100%', 'height': 60}),

html.Div([
dcc.Input(id=‘search’, type=‘hidden’),

                html.Div(id="show-jobs", children=[]),



                ]),



])

modal= html.Div([
dbc.Modal(
[
dbc.ModalHeader(“Header”),
dbc.ModalBody(“This the Modal Body”),
dbc.ModalFooter(
dbc.Button(“Close”, id=“close”, className=“ml-auto”)
),
], id=“modal”

                )])

app.title = ‘CAS System Jobs’

@app.callback(Output(‘show-jobs’, ‘children’),
[Input(‘search’, ‘value’)])
def load_initial_job_page(value):
joblist = []
for i in range(totalJobs):
joblist.append(
html.Div([dbc.Card([dbc.CardBody([
html.P(df[‘JOB_NAME’][i], className=“card-title”, style={‘fontSize’: 14, ‘fontWeight’: ‘bold’}),
html.Label("Status: ")
, html.P(df[‘JOB_STATUS’][i], className=“card-text”),

            dbc.Button("Job Details", color="primary", id="open",n_clicks=0,className="mt-auto")])]
            , className="", outline=True,
            style={'width': "30rem", 'height': '16rem'}, color="success"
        )



        ], className="column"))
return joblist

@app.callback(
Output(“modal”, “is_open”),
[
Input(“open”, “n_clicks”),
Input(“close”, “n_clicks”)
],
[State(“modal”, “is_open”)]
)
def get_selected_data1(open_click,close_click,is_open):
if open_click or close_click:
return not is_open
return is_open

So right now I am not loading Modal window in my Layout( in my previous trials I have done that but did not work) but just storing it in a modal variable.

Can someone suggest me how to include this in my page layout once the callback is accepted by button with id=“open” (Job Details).

please answer these:

1.That button is not firing the call back
2. This button has same id so many times on the same page but DASH did not throw error for duplicate button Id.? why?

Here is the snapshot from my page as well.

I just tried running your app (with some dummy data in place of your df). I assume you must have set app.config.suppress_callback_exceptions = True to get it running? Which explains

1.That button is not firing the call back

The modal is not in the layout. Any callback that can’t find its output, or one or more of its inputs / states in the layout is not going to be triggered, even if one of the other inputs fires. To make the modal appear it has to be in the layout.

  1. This button has same id so many times on the same page but DASH did not throw error for duplicate button Id.? why?

Not entirely sure, but I think it’s to do with callback exceptions being suppressed.

The main problem you have is that you are only creating a single modal and several buttons with the same id. Instead you want to make a modal per job card, and attach a callback to each of those. Check out this modified version of your example.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import pandas as pd
import math

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])

app.config.suppress_callback_exceptions = True

df = pd.DataFrame(
    {
        "JOB_NAME": ["A", "B", "C", "D", "E", "F", "G", "H"],
        "JOB_STATUS": [1, 2, 3, 4, 5, 6, 7, 8],
        "JOB_DETAILS": ["A1", "B2", "C3", "D4", "E5", "F6", "G7", "H8"],
    }
)
rowCount = math.ceil(df.shape[0] / 4)
totalJobs = df.shape[0]


def make_job_card(i):
    return html.Div(
        [
            dbc.Card(
                [
                    html.P(
                        df["JOB_NAME"][i],
                        className="card-title",
                        style={"fontSize": 14, "fontWeight": "bold"},
                    ),
                    html.Label("Status: "),
                    html.P(df["JOB_STATUS"][i], className="card-text"),
                    dbc.Button(
                        "Job Details",
                        color="primary",
                        id=f"open-{i}",
                        n_clicks=0,
                        className="mt-auto",
                    ),
                ],
                body=True,
                outline=True,
                style={"width": "30rem", "height": "16rem"},
                color="success",
            ),
            dbc.Modal(
                [
                    dbc.ModalHeader("Header"),
                    dbc.ModalBody(df["JOB_DETAILS"][i]),
                    dbc.ModalFooter(
                        dbc.Button(
                            "Close", id=f"close-{i}", className="ml-auto"
                        )
                    ),
                ],
                id=f"modal-{i}",
            ),
        ],
        key=str(i),
        className="column",
    )


app.layout = html.Div(
    [
        html.Div(
            html.H2(
                "CAS System Jobs",
                style={
                    "color": "white",
                    "marginLeft": "3%",
                    "marginTop": "1%",
                },
            ),
            style={
                "background": "#139847",
                "float": "left",
                "marginBottom": 10,
                "width": "100%",
                "height": 60,
            },
        ),
        html.Div(
            [
                dcc.Input(id="search", type="hidden"),
                html.Div(id="show-jobs", children=[]),
            ]
        ),
    ]
)


@app.callback(Output("show-jobs", "children"), [Input("search", "value")])
def load_initial_job_page(value):
    joblist = []
    for i in range(totalJobs):
        joblist.append(make_job_card(i))
    return joblist


def get_selected_data1(open_click, close_click, is_open):
    if open_click or close_click:
        return not is_open
    return is_open


# attach the callback to each modal / button pair
for i in range(totalJobs):
    app.callback(
        Output(f"modal-{i}", "is_open"),
        [Input(f"open-{i}", "n_clicks"), Input(f"close-{i}", "n_clicks")],
        [State(f"modal-{i}", "is_open")],
    )(get_selected_data1)


if __name__ == "__main__":
    app.run_server(debug=True)

Thank you very much tcbegley for replying so fast…

I tried to put Modal in the layout and one Modal for each Job card before posting this question but for that I was not using any dynamic id for buttons like you did. And I was not sure how to do a callback for so many ids. But your reply sorted that issue as well.

The issue I am facing right now is it is taking very long time to load the page and I guess because the app.callback is put into the loop and my totaljobs count is more than 500.
I tried to load the page in debug and it loaded but then it failed to close the Modal as the screen got freezed out.
See the attachment.
I have tried few solutions like changing the z-index as mentioned in few posts for this issue but was not able to get through.

Can you please help me with these:

  1. how can I load the page fast with job cards more than 500
  2. How can I fix this freezing of the Modal window (see the attachment)

Thanks in advance!!!

One of the reasons for the slow load of the page is that on start up, dash will fire all defined callbacks with their default values. Since you have 500 callbacks, even if each one took only 10ms to complete, that’s already 5 seconds.

I suggest you do two things:

  1. Rather than displaying all 500 jobs, try paginating them, so that you only ever have 50 say on a single page.
  2. Rather than doing what I first suggested and defining lots of callbacks in a loop, we can build a single callback with lots of inputs and outputs (see below example). You can use dash.callback_context to figure out which component to update. Then you don’t have the overhead of waiting for tonnes of callbacks to fire when the app loads, you just have to wait for that one callback to fire. This worked pretty well for me with 50 jobs. It starts to slow down when you have 500, which is where pagination will help. You can define one of these monster callbacks per page of jobs.
import math
from string import ascii_uppercase

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])

app.config.suppress_callback_exceptions = True

N = 50

df = pd.DataFrame(
    {
        "JOB_NAME": [ascii_uppercase[i % 26] for i in range(N)],
        "JOB_STATUS": [i for i in range(N)],
        "JOB_DETAILS": [f"{ascii_uppercase[i % 26]}{i}" for i in range(N)],
    }
)
rowCount = math.ceil(df.shape[0] / 4)
totalJobs = df.shape[0]


def make_job_card(i):
    return html.Div(
        [
            dbc.Card(
                [
                    html.P(
                        df["JOB_NAME"][i],
                        className="card-title",
                        style={"fontSize": 14, "fontWeight": "bold"},
                    ),
                    html.Label("Status: "),
                    html.P(df["JOB_STATUS"][i], className="card-text"),
                    dbc.Button(
                        "Job Details",
                        color="primary",
                        id=f"open-{i}",
                        n_clicks=0,
                        className="mt-auto",
                    ),
                ],
                body=True,
                outline=True,
                style={"width": "30rem", "height": "16rem"},
                color="success",
            ),
            dbc.Modal(
                [
                    dbc.ModalHeader("Header"),
                    dbc.ModalBody(df["JOB_DETAILS"][i]),
                    dbc.ModalFooter(
                        dbc.Button(
                            "Close", id=f"close-{i}", className="ml-auto"
                        )
                    ),
                ],
                id=f"modal-{i}",
            ),
        ],
        key=str(i),
        className="column",
    )


app.layout = html.Div(
    [
        html.Div(
            html.H2(
                "CAS System Jobs",
                style={
                    "color": "white",
                    "marginLeft": "3%",
                    "marginTop": "1%",
                },
            ),
            style={
                "background": "#139847",
                "float": "left",
                "marginBottom": 10,
                "width": "100%",
                "height": 60,
            },
        ),
        html.Div(
            [
                dcc.Input(id="search", type="hidden"),
                html.Div(id="show-jobs", children=[]),
            ]
        ),
    ]
)


@app.callback(Output("show-jobs", "children"), [Input("search", "value")])
def load_initial_job_page(value):
    joblist = []
    for i in range(totalJobs):
        joblist.append(make_job_card(i))
    return joblist


@app.callback(
    [Output(f"modal-{i}", "is_open") for i in range(totalJobs)],
    [Input(f"open-{i}", "n_clicks") for i in range(totalJobs)]
    + [Input(f"close-{i}", "n_clicks") for i in range(totalJobs)],
    [State(f"modal-{i}", "is_open") for i in range(totalJobs)],
)
def get_selected_data1(*args):
    ctx = dash.callback_context

    if not ctx.triggered or ctx.triggered[0]["value"] == 0:
        raise PreventUpdate
    else:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]

    i = int(button_id.split("-")[1])
    states = args[-N:]
    return [s if i != j else not s for j, s in enumerate(states)]


if __name__ == "__main__":
    app.run_server()

The new problem that this introduces is that I think (based on the search bar in your app) you will want to filter the jobs, and the approach I’ve outlined won’t allow you to do that particularly easily.

The best way around all of this that I can see would be to write your own Modal component using the dash component boilerplate that could manage the modal triggering without the need for a callback. I don’t think this would be too hard… but I don’t know how comfortable you are writing some React / javascript.

Thanks Tom for again a quick response.
I will try what you have mentioned and yes I do not have any knowledge on React as I am from a legacy background(AS400) just trying my hands over here.

I was trying few things last night and now the modal window is showing up and is closing on the close button click as well but again the time taking in opening the modal (11secs) and closing it is like 10 sec. may be this is because of the mutiple callbacks?

Also page load became little faster but I can utilize the pagination and single call back with mutiple out/inputs…

Hey Sandy! How you solved the Modal problem?

I’m facing this exactly problem at this moment.

Hello Leonardo,

Please check the post 3 in the same thread.

Using context and assigning id to each was the solution but still i feel having more modal components will slow loading and response time.