Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

How to work with dynamic callbacks in Dash?

I have a Dash application where the items of a ListGroup are generated dynamically, based on the length of a certain file. The file can have different lengths. I have added the items to the list, each of them having a unique id, based on its position in the file.
Here I am appending the items from file to the list group.

output.append(dbc.ListGroupItem(row_value, id=str(row_number), n_clicks=0, action=True))

I have a callback where I need to know the length of listgroup items, but I don’t know the length of them in the Input of the callback. How can I solve this problem?
This is the dynamic callback:

@app.callback(
    [Output("counter", "children"), Output("sparql_query", "value")],
    [Input(str(i), "n_clicks") for i in range(prefixes_count)],
    [Input(str(i), "children") for i in range(prefixes_count)],
    State('sparql_query', 'value'), prevent_initial_call=True
)

I need to set the prefixes_count beforehand. I was thinking of using a helper div where to store the prefixes_count value, but how can I access it in the previous callback ? I was thinking of something like this, but obviously this is not working:

range(Input('length_children_tab', 'children'))

How can I reach the same effect?

Hi lola_bunny,
Althought in the documentation prevents the use of Global variables, it seams to me that in your case it will be a right solution.
Unless exists a way to take the info from a Div store (without using a callback), that should be very helpfull, but I do not know that there is one.

See pattern matching callbacks: https://dash.plotly.com/pattern-matching-callbacks :slight_smile:

How can I trigger the change of the prefixes_count if I update its value in another callback function, based on the change of the ‘helper’ html div?

@app.callback(Output("helper", "children"),
              [Input('list_group', 'children'), Input('length_children_tab', 'children')])
def populate(list_group_children, length_children_tab):
    print("list_group_children", list_group_children)
    print("length_children_tab", length_children_tab)
    trigger = callback_context.triggered[0]
    global prefixes_count
    prefixes_count = trigger['value']
    print("TRIGGER iS ", trigger['value'])
    print("PREFIXES FROM TRIGGER iS ", prefixes_count)
    return "UPDATED length is  ", length_children_tab

Everytime this callback change the prefixes_count, the ‘helper’ ‘children’ also change, that means that you can trigger a callback everytime the ‘helper’ ‘children’ changed.
Hope I understood your question.

I see, but I am new to Dash and I don’t know how to achieve this :slight_smile:

lola_bunny,

I never worked with this kind of input in a callback:

@app.callback(
    [Output("counter", "children"), Output("sparql_query", "value")],
    [Input(str(i), "n_clicks") for i in range(prefixes_count)],
    [Input(str(i), "children") for i in range(prefixes_count)],
    State('sparql_query', 'value'), prevent_initial_call=True
)

I answer your questions trying to follow your needs.
If you can add a simple example that can be copied and pasted perhaps I can find a solution, or explain a little bit what do you want to accomplish and why you need the lenght, perhaps could be other options.

Okay, so I uploaded my code on github: https://github.com/iulianastroia/dash_app. The app is to_send.py, please ignore the id errors, the main application is bigger and structured over many files. I extracted only a bit of it. So basically i need to update the id of the elements of the group list, so that the number of id(list items) is equal to the number of lines from the chosen file. The files are 3 and their length differ, they are dynamically updated.

Also, whenever I click on an option from the list, it is copyed in the left side of the page.

Hi lola_bunny,

I have the answer you are looking for:

Add:

import dash_core_components as dcc

Replace:

html.Div(id='length_children_tab')

for:

dcc.Input(id='length_children_tab')

Change the second Output in the first callback for:

Output('length_children_tab', 'value')]

Add in the second Callback:

[Input('length_children_tab', 'value')],

And then the args[-2] gives the number you are looking for.

The dcc.Input ‘value’ property stores the len from the first callback and then provides it for the second.

Great, thanks! But still, how can I get the length of length_children_tab in the second callback as the length is needed here:

    [Input(str(i), "n_clicks") for i in range(prefixes_count)],
    [Input(str(i), "children") for i in range(prefixes_count)],

How can I know before how many items i have in the groupList? prefixes_count does not update its value when changing length_children_tab…

I found the solution and i updated it here if you want to take a look:
This is the application from github


import dash_html_components as html
from dash import callback_context
from dash.dependencies import Input, Output, State
import yasqe_dcc

import dash_bootstrap_components as dbc
import dash

# style to vertically split the screen in two parts
SPARQL_INPUT_STYLE = {'position': 'relative', 'width': '50%', 'float': 'left', 'height': '10%'}
PROPERTIES_STYLE = {'width': '49%', 'display': 'inline-block', 'height': '259px', 'overflow': 'scroll'}

app = dash.Dash(
    external_stylesheets=['//cdn.jsdelivr.net/npm/yasgui-yasqe@2.11.22/dist/yasqe.min.css',
                          dbc.themes.BOOTSTRAP])

# path changes, based on click on "classes", 'prefixes' and 'properties'
PATH_TO_PREFIXES_FILE = r"prefixes_tab.txt"

# needs to change based on file length
prefixes_count = len(open(PATH_TO_PREFIXES_FILE, "r").readlines())


def read_file(tab_name):
    prefixes_file = open(str(tab_name) + ".txt", "r")

    # holds prefixes read from file
    output = []
    for row_number, row_value in enumerate(prefixes_file):
        # append prefixes from file to list
        # get value without \n
        # output.append(dbc.ListGroupItem(row_value.splitlines(), id=str(row_number), n_clicks=0, action=True))
        if row_number == 0:
            output.append(
                dbc.ListGroupItem(row_value, id={"item": str(row_number)}, n_clicks=0, action=True, disabled=True))       
        else:
            output.append(dbc.ListGroupItem(row_value, id={"item": str(row_number)}, n_clicks=0, action=True))
    return output


"""
set layout 
"""
app.layout = html.Div(
    style={'fontFamily': 'Bahnschrift'},
    children=[
        html.H3(children='Select text from list to view into textInput',
                style={'color': 'rgba(0,102,102,1)',
                       'textAlign': 'center'}),
        # SPARQL interpreter (left side of screen)
        html.Div(
            children=[
                yasqe_dcc.YasqeDcc

                    (
                    id='sparql_query',
                    value=" "
                ),

            ],
            style=SPARQL_INPUT_STYLE

        ),
        # WIKI tabs (right side of screen)
        html.Div(children=[
            dbc.Tabs
            (id="wiki_tabs", active_tab='prefixes_tab',
             children=[
                 dbc.Tab(label='Classes', tab_id='classes_tab'),
                 dbc.Tab(label='Prefixes', tab_id='prefixes_tab'),
                 dbc.Tab(label='Properties', tab_id='properties_tab'),
             ], ),
            html.Div(id='wiki_tabs_content')
        ],
        ),
        dbc.Button('Submit Query', id='submit', n_clicks=0, color="info",
                   className="mr-1", style={'margin': '0 auto', 'width': '150px',
                                            'textAlign': 'center',
                                            }),

        html.Div(id='view_query',
                 style={'whiteSpace': 'pre-line', 'textAlign': 'center',
                        'color': 'rgba(0,102,102,1)'}),
        html.Div(id='length_children_tab'

                 )
        , html.Div(id='helper'),
        dbc.ListGroup(id='list_group')

    ])

@app.callback(
    [Output('wiki_tabs_content', 'children'), Output('length_children_tab', 'value')],
    [Input('wiki_tabs', 'active_tab')])
def render_content(tab):
    output = read_file(tab)
    prefix_list = [dbc.ListGroup(id="list_group",
                                 children=
                                 output, style=PROPERTIES_STYLE
                                 ),
                   ]
    return prefix_list, len(output)


@app.callback(
    [Output("helper", "children"), Output("sparql_query", "value")],
    [Input({"item": ALL}, "n_clicks")],
    [Input({"item": ALL}, "children")],
    [Input('length_children_tab', 'value')],
    State('sparql_query', 'value'), prevent_initial_call=True
)
def func(*args):
    print('len', len(args))
    print("args[1]",args[1])
    length_children_tab = args[-2]
    print("args", args)
    print("callback context ", callback_context.triggered[0])
    trigger = callback_context.triggered[0]
    selected_value = trigger["prop_id"].split(".")[0]
    selected_from_value = selected_value.split(":")[1]
    SELECTED_ID = selected_from_value.split('"')[1]
    print("SELECTED ID IS ", SELECTED_ID)
    # SELECTED_VALUE=args[1][int(SELECTED_ID)]


    #
    if int(SELECTED_ID) == 0:
        raise dash.exceptions.PreventUpdate()
    try:
        print("selected option is ", SELECTED_ID)
        # get selected item from prefixes list based on the selected id
        # args is a list of all the ids, followed by the prefixes value
        SELECTED_VALUE = args[1][int(SELECTED_ID)]
        # sparql_query = args[len(args) - 1] + "\n" + selected_value
        # sparql query holds the query from yasqe, followed by the chosen prefix from the list
        sparql_query = args[len(args) - 1] + SELECTED_VALUE
        print("sparql query is ", sparql_query)
        print("selected option is ", )
    except ValueError:
        pass
    return SELECTED_ID, sparql_query


if __name__ == '__main__':
    app.run_server(debug=True, port=8019)

Glad to here that !!! :smiley:
chriddyp is the author of Dash, he knows a little bit more than me. :laughing: :laughing: :joy:

Everytime this callback change the prefixes_count, the ‘helper’ ‘children’ also change, that means that you can trigger a callback everytime the ‘helper’ ‘children’ changed.