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

Controls are not getting added to the respective dynamic groups (Instead adding to the both dynamic group

I am trying to build a site which needs to have the group of dynamic controls in the dbc.card. When I try to add the controls to dynamic dbc.card , it is not adding to it.(adding to the both the groups) I would appreciate the help. I have been working on this for 2 days, I couldnt get it working (may be I am new to dash). enter image description here

import dash
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL
import logging as logger
import json
from dash.exceptions import PreventUpdate
import numpy as np
import sqlite3
import random as rangead
import string
import sys
import pandas as pd

logger.basicConfig(
    format='%(levelname)s %(asctime)s %(filename)s [%(name)s] %(funcName)s | %(message)s')
logger.root.level = logger.DEBUG

external_stylesheets = [
    {
        "href": "/assets/style.css"
        "family=Lato:wght@400;700&display=swap",
        "rel": "stylesheet",
    },

]

app = dash.Dash(__name__)

def generateCardtoCollapse(triggered, n_addbank_clicks, n_addgrp_clicks):
    addgroupSubcontrols = html.Div([dbc.Row([
        dbc.Col(
            dbc.Label("Group", id={'type': 'group-label',
                      'index': n_addbank_clicks + n_addgrp_clicks}),
        ),
        dbc.Col(
            dbc.Button(
                "+/-",
                id={'type': 'collapse-button',
                    'index': n_addbank_clicks + n_addgrp_clicks},
                className="mr-1",
                outline=True,
                color="danger",
                n_clicks=0,
            ),
        )]),
        dbc.Row([
            dbc.Col(
                html.Br()
            )],
        align="left",
        no_gutters=True,
    )])


    control1 = html.Div([
    addgroupSubcontrols,
    dbc.Collapse(dbc.Card(dbc.CardBody([
    html.Div(children=[
                html.Div(
                    id={
                        'type': 'innner_dynamic_controls',
                                'index': n_addbank_clicks + n_addgrp_clicks
                    }
                ),
                html.Br(),
                html.Br(),
            ]),

    ])),id={'type': 'collapse','index': n_addbank_clicks + n_addgrp_clicks},is_open=False)     
    ])

    return control1

def generateControls(triggered, n_addbank_clicks, n_addgrp_clicks):    
 
    addparentgroup= dbc.Row([
        dbc.Col(
            dcc.RadioItems(
                id={'type': 'add_rdo_1',
                            'index': n_addbank_clicks + n_addgrp_clicks},
                options=[
                    {"label": "ANY", "value": 'any'},
                    {"label": "ALL", "value": 'all'}
                ],
                className="form-check-label",
                value='all',
                labelStyle={'display': 'inline-block'},
            ))
       ],
        align="left",
        no_gutters=True,
    )

    addbankSubControls = dbc.Row([
        dbc.Col(
            html.Div(children=[
                html.Div(id={'type': 'add_rdo_1', 'index': n_addbank_clicks +
                                     n_addgrp_clicks}, style={"color": "blue", "fontSize": "12px"})
            ])
        )],
        align="left",
        no_gutters=True,
    )

    subcontrols = dbc.Row([
        dbc.Col(
            addparentgroup if triggered == 'rgp_add_group' else addbankSubControls,
        ),
        dbc.Col(
            dcc.Input(
                id={
                    'type': 'txt_BoonName',
                    'index': n_addbank_clicks + n_addgrp_clicks
                },
            )),
        dbc.Col(
            dcc.Dropdown(
                id={
                    'type': 'dynamic-dropdown',
                    'index': n_addbank_clicks + n_addgrp_clicks
                },
                options=[{'label': i, 'value': i}
                         for i in ['NYC', 'MTL', 'LA', 'TOKYO']]
            )),
        dbc.Col(
            dcc.Input(
                id={
                    'type': 'txt_group',
                    'index': n_addbank_clicks + n_addgrp_clicks
                },
                value=n_addgrp_clicks,
                disabled=True
            )),
        dbc.Col(
            html.Div(children=[
                html.Div(
                    id={
                        'type': 'dynamic_output_of_controls',
                                'index': n_addbank_clicks + n_addgrp_clicks
                    }
                ),
                html.Br(),
                html.Br(),
            ]),
        ),
    ])
    control1 = html.Div([subcontrols])

    # control1 = html.Div([
    #     # dbc.Collapse(dbc.Card(dbc.CardBody([subcontrols])),id={'type': 'collapse','index': n_addbank_clicks + n_addgrp_clicks},is_open=False)
    #     # if triggered == 'rgp_add_group' else
    #     dbc.Collapse(
    #         dbc.Card(dbc.CardBody( 
                
    #             html.Div(children=[
    #                 subcontrols                
    #             ],id={
    #                     'type': 'subcontrols_m',
    #                             'index': n_addbank_clicks + n_addgrp_clicks
    #                 }
    #         ), )),            
    #         id={
    #             'type': 'collapse','index': n_addbank_clicks + n_addgrp_clicks
    #             },
    #         is_open=True,
    #     ),
        
    # ])

    return control1


app = dash.Dash(__name__, external_stylesheets=[
                dbc.themes.SIMPLEX], suppress_callback_exceptions=True)

app.layout = html.Div([
    dbc.Row([
        dbc.Col(
            html.Div([
                dbc.ButtonGroup(
                     [
                         dbc.Button(id="rgp_add_group", children="add bank group", outline=True,
                                    color="info", className="mr-1", n_clicks=0),
                         dbc.Button(id="rgp_add_bank", children="add bank", outline=True,
                                    color="primary", className="mr-1", n_clicks=0, style={"marginleft": "15px"}),
                         dbc.Button(id="rgp_view_bank", children="view banks", outline=True,
                                    color="danger", className="mr-1", n_clicks=0, style={"marginleft": "15px"}),
                         dbc.Button(id="rgp_del_bank", children="delete", outline=True,
                                    color="danger", className="mr-1", n_clicks=0, style={"marginleft": "15px"}),
                         dbc.Button(id="rgp_submit_button", type='submit',
                                    children="Submit", className="mr-1",  outline=True, color="success", style={"marginleft": "15px"})
                     ],
                     size="sm",
                     ),

            ]),
        ),
    ]),
    dbc.Row([
        dbc.Col(
            html.Div([
                html.Br(),
                html.Br(),
                dbc.Card(
                    dbc.CardBody(
                        [                           
                            html.Div(id='dynamic_control_create_container', children=[]),
                        ]
                    )
                )
            ]),
        ),
    ]),
])


@app.callback(
    Output('dynamic_control_create_container', 'children'),
    Input('rgp_add_group', 'n_clicks'),
    State('rgp_add_bank', 'n_clicks'),    
    State('dynamic_control_create_container', 'children'))
def display_dropdowns_1(n_addgrp_clicks, n_addbank_clicks, children):
    ctx = dash.callback_context
    triggered = ctx.triggered[0].get("prop_id").split(".")[0]
    print("Triggered dynamic_control_create_container ", triggered, "n_addgrp_clicks", n_addgrp_clicks, "n_addbank_clicks",
          n_addbank_clicks, "n_delbank_clicks")
    
    if triggered == 'rgp_add_group':
        new_element = generateCardtoCollapse(triggered,n_addbank_clicks, n_addgrp_clicks)
        children.append(new_element)
    
    return children

@app.callback(
    Output({'type': 'innner_dynamic_controls', 'index': MATCH}, 'children'),            
    Input('rgp_add_bank', 'n_clicks'),
    Input('rgp_view_bank', 'n_clicks'),
    Input('rgp_del_bank', 'n_clicks'),
    Input('rgp_submit_button', 'n_clicks'),
    State('rgp_add_group', 'n_clicks'),
    State({'type': 'innner_dynamic_controls', 'index': MATCH}, 'children'))    
def display_dropdowns( n_addbank_clicks, n_viewbank_clicks, n_delbank_clicks, n_submitbank_clicks,n_addgrp_clicks, children):
    ctx = dash.callback_context
    triggered = ctx.triggered[0].get("prop_id").split(".")[0]
    print("Triggered COLLAPSE ", triggered, "n_addgrp_clicks", n_addgrp_clicks, "n_addbank_clicks",
          n_addbank_clicks, "n_delbank_clicks", n_delbank_clicks, "n_submitbank_clicks", n_submitbank_clicks ,"children",  children )

    if children is None:
        children=[]

    if triggered == 'rgp_add_bank':
        new_element = generateControls(triggered, n_addbank_clicks, n_addgrp_clicks)

        children.append(new_element)
 

    if triggered == 'rgp_del_bank':
        children = children[:-1]

    return children
    

@app.callback(
    Output({'type': 'dynamic_output_of_controls', 'index': MATCH}, 'children'),
    Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value'),
    State({'type': 'dynamic-dropdown', 'index': MATCH}, 'id'),
    Input({'type': 'add_rdo_1', 'index': MATCH}, 'value'),
    State({'type': 'add_rdo_1', 'index': MATCH}, 'id'),
    Input({'type': 'txt_BoonName', 'index': MATCH}, 'value'),
    State({'type': 'txt_BoonName', 'index': MATCH}, 'id'),
)
def display_output(value, id, rdovalue, rdoid, txtvalue, txtid):
    # return html.Div('Dropdown {} = {}'.format(id['index'], value) + '|Radio {} = {}'.format(rdoid['index'], rdovalue) \
    #      + '|TextBox {} = {}'.format(txtid['index'], txtvalue))
    return ('Dropdown {}'.format(value) + '|Radio {}'.format(rdovalue)
            + '|TextBox {}'.format(txtvalue)).split('|')

 

@app.callback(
    Output({'type': 'collapse', 'index': MATCH}, 'is_open'),
    [Input({'type': 'collapse-button', 'index': MATCH}, 'n_clicks')],
    [State({'type': 'collapse', 'index': MATCH}, 'is_open')],
)
def toggle_collapse(n, is_open):
    print("I am trying to collapse")
    if n:
        return not is_open
    return is_open


if __name__ == '__main__':
    app.run_server(debug=True)
![Issue|690x272](upload://zesNXZUyUIFvJ3E0ImY2DiliznF.png)

Hi @akiwaga.morgan!

That’s a very looong application to look into, but I think I know what your problem is. :smiley:

You have this callback:

@app.callback(
    Output({"type": "innner_dynamic_controls", "index": MATCH}, "children"),
    Input("rgp_add_bank", "n_clicks"),
    Input("rgp_view_bank", "n_clicks"),
    Input("rgp_del_bank", "n_clicks"),
    Input("rgp_submit_button", "n_clicks"),
    State("rgp_add_group", "n_clicks"),
    State({"type": "innner_dynamic_controls", "index": MATCH}, "children"),
)
def display_dropdowns(
    n_addbank_clicks,
    n_viewbank_clicks,
    n_delbank_clicks,
    n_submitbank_clicks,
    n_addgrp_clicks,
    children,
):

The last State passed in the decorator matches all elements with id/type “innner_dynamic_controls” (typo added), which are all internal divs to each one of your “Group” cards. So you are basically triggering the callback for each group all times and adding the same component to each group, therefore the duplication that you mentioned.

There are many ways of fixing it, but most of the alternatives would involve major refactorings in your application (it is very cluttered). A quick fix would be to replace MATCH by ALL in the callback signature, so:

@app.callback(
    Output({"type": "innner_dynamic_controls", "index": ALL}, "children"),
    Input("rgp_add_bank", "n_clicks"),
    Input("rgp_view_bank", "n_clicks"),
    Input("rgp_del_bank", "n_clicks"),
    Input("rgp_submit_button", "n_clicks"),
    State("rgp_add_group", "n_clicks"),
    State({"type": "innner_dynamic_controls", "index": ALL}, "children"),
)
def display_dropdowns(
    n_addbank_clicks,
    n_viewbank_clicks,
    n_delbank_clicks,
    n_submitbank_clicks,
    n_addgrp_clicks,
    all_children,
):
     # now all_children is a list with the inner controls of
     # each group...
     # you want to update the LAST VALUE of the list
     # so
    if all_children is None:
        all_children = []

    if len(all_children) == 0:
        children = []
    else:
        # grab last group
        children = all_children[-1]

    if triggered == "rgp_add_bank":
        new_element = generateControls(triggered, n_addbank_clicks, n_addgrp_clicks)

        children.append(new_element)
    
    
    if len(all_children) == 0:
        return [children]
    elif len(all_children) > 0:
        # replace the last element, matchin the last group
        all_children[-1] = children
       # return all group inner_controls, but just the last is modified
        return all_children

Note that with ALL you are explicitly passing all the components matching the id/type explicitly in a single callback, so the callback triggers just once. The caveat is that you just want to modify the last group of the list, so the rest remains unchanged.

You will end up with some problems regarding the selections in each component of the controls, because you are essentially rewriting all of them each time you add a bank. There are ways to mitigate that, but please ask another question if this pops up.

Hope that this helps!