Remove option from all other dropdowns in Dash

Viewed 833

When any option is selected from any one dropdown, I want that same option removed from all other dropdowns.

To help visualize this, I'm trying to build a column mapper. For each column name on the left, there's a dropdown to the right where a user will select the column name from the file they have uploaded that corresponds to the name on the left. Since the source and destination columns have a one-to-one relationship, when a column name is selected from one of the dropdowns, it can be removed from all the other ones.

As a proof of concept, I tried this on just one of the dropdowns using the code below. When I select an option from any of the other dropdowns, I expect the option to be removed from this particular one. I don't get any errors and the callback appears in the callback graph, but it doesn't do anything. The print statements in the callback don't even run, which means the callback isn't actually running at all.

def generate_output_callback(input_id):
    def dd_callback(input_values, state):
        if not input_values:
            print('Preventing update.'.upper())
            raise PreventUpdate
        else:
            print(f'callback ran for dropdown {input_id}')
            for val in input_values:
                if val:
                    try:
                        i = state.index({'label': val, 'value': val})
                        del state[i]
                    except ValueError:
                        pass
            return state
    return dd_callback


all_cols = ['ACCTNUMBER', 'col2', 'col3']
inputs = {}
for col in all_cols:
    inputs[col] = Input(col, 'value')
all_other_inputs_dict = {k:v for (k,v) in inputs.items() if k != 'ACCTNUMBER'}
all_other_inputs = list(all_other_inputs_dict.values())
cb = generate_output_callback('ACCTNUMBER')
app.callback(
    Output('ACCTNUMBER', 'options'),
    all_other_inputs,
    [State('ACCTNUMBER', 'options')])(cb)
1 Answers

I would dump the callback you have now. It's already pretty complex, and you still have to add more dropdowns. The logic you need will require every dropdown to affect every other dropdown, which could easily become a loop without the right structure.

For this, a hidden element would work. The hidden element would hold all of the valid dropdown values. Every callback would listen to it and update their options props based on it. As the user selects columns from the various dropdowns, the hidden element, which should listen to all of the dropdowns' value props, will update to remove items which have already been selected. If the user unselects a column from a dropdown, the hidden element can then add that back in, and all of the options will update to add it back as well.

Edit:

Here is an example of what I mean:

@app.callback(Output('my-store', 'data'),
              [Input('my-dropdown-1', 'value'),
               Input('my-dropdown-2', 'value')],
              [State('my-store', 'data')])
def callback_func_update_store(dropdown_1, dropdown_2, store_state):
    original_options = store_state['original']  # so we can go back to the full set
    restricted_options = [x for x in original_options 
                          if x['value'] != dropdown_1 
                          and x['value'] != dropdown_2]
    return dict(
        original_options=original_options,
        restricted_options=restricted_options,
    )


@app.callback([Output('my-dropdown-1', 'options'),
               Output('my-dropdown-2', 'options')]
              [Input('my-store', 'data')],
              [State('my-dropdown-1', 'value'),
               State('my-dropdown-2', 'value')])
def callback_func_update_dropdown_options(store, dropdown_1, dropdown_2):
    dropdown_1_options = [x for x in store['original_options']
                          if x in store['restricted_options']
                          or x['value'] == dropdown_1]
    dropdown_2_options = [x for x in store['original_options']
                          if x in store['restricted_options']
                          or x['value'] == dropdown_2]
    
    return dropdown_1_options, dropdown_2_options

I haven't run this code, so it may not be perfect, but I hope it gives you the idea.

Related