Dynamically Generating Components, Reading User Input, and Saving User Input

Say I want to generate a checkbox/list for music genres in a list and for each genre I have multi-dropdown lists that has songs of that genre.

In this case, I don’t know which music genres are included in the genre list or how many there are. It can change over time. That’s why I can’t hard code a list of genres and can’t include the checkboxes and dropdown lists in the page layout.

I generate a checkbox and a multi dropdown list per row - each checkbox has a multi-dropdown list next to it. I wrote following function to dynamically generate components. ‘{}_box’ and ‘{}_multi’ where {} corresponds to the genre.

def generate_checkbox(genres, songs):
    '''Returns a checkbox component and a multidropdown
    list containing song names for each music genre'''
    div_list = []
    for each_genre in genres:
        div = html.Div([
            html.Div([
                html.Div([
                    dcc.Checklist(id='{}_box'.format(each_genre),
                                  options=[{'label': each_genre, 'value': each_genre}],
                                  value=[each_genre])
                ], className='six columns'),
                html.Div([
                    dcc.Dropdown(id='{}_multi'.format(each_genre),
                                 options=[{'label': i, 'value': i} for i in songs],
                                 multi=True,
                                 value=[])
                ], className='six columns'),
            ], className='row'),
            html.Br()
            ], className='row')
        div_list.append(div)
    return div_list

All genres are selected as default. User can check/uncheck genres and select one or more songs per genre.
What I want to do is to read input and generate a list of dictionaries:

user_selection = [{'Genre': 'Jazz', 'Songs': 'Jazz 1, Jazz 2'}, 
                          {'Genre': 'Classical', 'Songs': 'Classical 1'}, 
                          {'Genre': 'Rock', 'Songs': 'Rock 1, Rock 2, Rock 3'}]

I unfortunately can’t use the following loop since this loop returns data to dcc.store many times which gives an error (you can’t have multiple callbacks to the same output).

for each_item in GENRES:
    @app.callback(Output('store_info', 'data), 
                  [Input('{}_box'.format(each_item), 'value'), Input('{}_multi'.format(each_item), 'value')])
    def disable_multi_dropdowns(value_box, value_multi):
        '''Store selections in a dcc.store component'''
        if value_box == []:
            return None  #this is the case where the checkbox is unchecked
        elif value_box != [] and value_multi != []:   
        #this is the case where the box is checked and songs are selected in the multi-dropdown list
            dict = {'Genre': value_box, 'Songs': value_multi}
            return dict

It would be great if I could initially store a list of a dictionary in dcc.store component that has the first selection of genre and songs. After this, each time the loops runs, I could append the list with a new dictionary but don’t know if this is possible with dcc.store.

I actually found a solution to this but if you have ideas, feel free to share

@nilster - What was your solution?

@vantaka2 have a look

checkbox_list = [State('{}_box'.format(each_item), 'value') for each_item in GENRES]
dropdown_list = [State('{}_multi'.format(each_item), 'value') for each_item in GENRES]

def zip_genres(checklist, dropdown):
    combined = []
    for each_box, each_list in zip(checklist, dropdown):
        combined.append(each_box)
        combined.append(each_list)
    return combined

combined_list = zip_genres(checkbox_list, dropdown_list)

@app.callback(Output('store_info', 'data'), [Input('save', 'n_clicks'), Input('reset', 'n_clicks')], combined_list)
def save_selections(save, reset, *args):
    '''Save genres and corresponding songs in store_info dcc.store component'''
    list_of_things = []
    if save == 1:
        for i in range(0, len(args), 2):
            if args[i] != []:
                dictionary = {args[i][0]: args[i+1]}
                list_of_things.append(dictionary)
        return list_of_things
    elif reset:
        return []

My friend and I came up with this solution where you create a list of states that only has checkboxes and a list of states that only has multi dropdown lists.

Since I want a list of dictionaries in the format [{GENRE: SONGS}, {GENRE: SONGS}], I create a combined list which has the format [box, list, box, list, box, list, …].

After that I put this combined list in the app.callback along with save and reset button n_clicks.

If save is clicked (n_clicks can only be 1 or 0 in this case), look at the states 0,2,4,6 in the combined list. Even numbers correspond to boxes, odd numbers correspond to dropdown lists.

If a checkbox is checked, it should not have an empty list as value, therefore, if the box does not have an empty list as value, create a dictionary that has box info and multi dropdown list info (if box corresponds to args[i] then list corresponds to args[i+1]).