Callback taking multiple inputs where some may or may not exist depending on radio-item selection

I am trying to create an application where based on a radio item selection, a particular html.Div() is returned. Now, there is a button which if clicked, performs a task and gives an output. The problem is how to give input to the callback function that would be triggered on clicking the button and takes input from the values of fields in the created html.Div(). For e.g. - lets say
I have a radio-button which has two options: ‘Default value’ and ‘manual selection.’ By default the ‘Default value’ is selected. On selection, either a dropdown or an input box is generated respectively. Additionally, I have a button called ‘submit.’

Now, how to write the callback function to perform a task when the ‘Submit’ button is clicked? The task takes the value of the dropdown or the value of the input box. So, apart from the n_clicks of the button, I also need to supply the values of dropdown or input box as inputs. How can I do that? When I provide all three values (button, dropdown, input box) as Input, I get the error of “A nonexistent object was used in an Input of a Dash callback” since either the input box or the dropdown is not present. How can I write an efficient callback in this case?

Hi,

It seems like you are looking for layout_validation, and you can find more information here.

In a nutshell, you can define it just like the regular layout, but add extra components that are added to it via other callbacks. This is a better approach than just remove the layout validation.

Hi @jlfsjunior,

I went through the link you provided and added the validation layout. But it seems not to work and I get the same error. I have provided my code for your reference if I am going wrong anywhere. Any help is much appreciated.

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, ALL, State, MATCH, ALLSMALLER
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform

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

modal = html.Div(dbc.Modal([dbc.ModalBody(id='modal-body'),
                            dbc.ModalFooter(id='modal-footer',
                                            children=dbc.Button(children='Close', id='close-modal', className='ml-auto',
                                                                n_clicks=0)),
                            ],
                           id='modal',
                           centered=True,
                           is_open=False,
                           backdrop='static',
                           keyboard=True,
                           fade=True,
                           scrollable=True
                           ),
                 )
modal_1 = 'Default selection'
modal_2 = 'Manual selection'

card = dbc.Card(dbc.CardBody(
    [
        dcc.Dropdown(id='src-vpc',
                     placeholder='select an item',
                     options=[{'label': 'label1', 'value': 'lable1'},
                              {'label': 'label2', 'value': 'lable2'},
                              {'label': 'label3', 'value': 'lable3'},
                              ],
                     value=0,
                     style={'color': 'black'},
                     ),
        dbc.RadioItems(id='radio-items',
                       options=[{'label': 'Default value', 'value': 'default'},
                                {'label': 'Enter manually', 'value': 'manual'}],
                       value='default',
                       inline=True
                       ),
        html.Div(id='container'),
        modal,
        html.Button('Submit', id='button', n_clicks=0)
    ]))

manual = html.Div([
    html.Br(),
    dbc.Input(id='sel-manual',
              placeholder='Enter a value',
              type='text',
              style={'color': 'black', 'width': '100%'}
              ),
])

default = html.Div([
    html.Br(),
    dcc.Dropdown(id='sel-default',
                 placeholder='select an item',
                 options=[{'label': 'dlabel1', 'value': 'dlable1'},
                          {'label': 'dlabel2', 'value': 'dlable2'},
                          {'label': 'dlabel3', 'value': 'dlable3'},
                          ],
                 value=0,
                 style={'color': 'black'}
                 ),
])

app.layout = html.Div(dbc.Row(dbc.Col(card, width=3)))
app.validation_layout = html.Div([card, modal, manual, default])


@app.callback(
    Output(component_id='container', component_property='children'),
    Input(component_id='radio-items', component_property='value'),
    prevent_initial_call=True
)
def div_destination_node(dst_node):
    print(f'Val of radio item: {dst_node}')
    if dst_node == 'manual':
        return manual
    else:
        return default


@app.callback(
    [
        Output('modal', 'is_open'),
        Output('modal-body', 'children'),
    ],
    [
        Input('sel-manual', 'value'),
        Input('sel-default', 'value'),
        Input('radio-items', 'value'),
        Input('button', 'n_clicks'),
        Input('close-modal', 'n_clicks'),
    ],
    prevent_initial_call=True
)
def radio_item_selection(val_manual, val_default, radio_item, n1, n2):
    print(f'radio_item value:{radio_item}')
    button_pressed = [p['prop_id'] for p in dash.callback_context.triggered][0]
    print(f'button_pressed: {button_pressed}')
    if 'close-modal' in button_pressed:
        return False, ''
    elif 'button' in button_pressed:
        if radio_item == 'default':
            print('value is hcloud')
            return True, modal_1
        else:
            print('value is manual')
            return True, modal_2
    return False, ''


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

Thanks!

I see… So, the problem of your app is that “sel-manual” and “sel-automatic” are never in the same layout, so there is always one of the Inputs missing in the callback and therefore the error.

In the lack of knowing a better solution, I can suggest one approach that works just fine, especially if you want to trigger the callback just when you press the button…

You can replace both “sel-manual” and “sel-automatic” by a State("container", "children"). Then this will return the manual or default component, which is a Div that has the second children an dbc.Input or dcc.Dropdown. What you want is their value, regardless of which one it is, so you can access in the callback via:

value = children["props"]["children"][1]["props"]["value"]

Note that while you have defined the component via python, it is returned as State (or Input) as a dictionary. The parameters of each component are called “props” and are under this key, including the children.

This is very nested and you need to be careful if there are edge cases in initial callbacks where the components and props are not defined.

Hope that this works for you!

EDIT: After reading your entire callback, I realized that you will also need the “id”, not only “value”… but I hope you get the gist

Hello @jlfsjunior,

Thank You so much. Its quite nested and nasty, but this is exactly what I needed! :slight_smile: