Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Return concatenated html objects from function call

I am trying to loop through a list and create a new div for each item in the list (the list length changes based on a drop down input). In order to do this, I am defining a function which takes the length of a list and then outputs a html object for each item in the list.

def create_divs(total_divs):
	i=0
	return_divs = []
	while i < total_divs:
		return_divs.append(html.Div(id='id-' + str(i), children=[html.Button('Reset', id='id_0_'+str(i), n_clicks=0)]))
		i += 1
	return return_divs

However, I am unable to actually cycle through this list when creating my dash.app layout. Any ideas for how I might either concatenate these return divs together, or iterate through the list when creating my layout?

Can you provide more context? What do you do with the create_divs function?

If this function is called with a parameter that varies based on some user input, then the typical way to do this is to create a callback with the dropdowns value property as an Input, and targets a particular children property of a node already in the layout. Your callback can then generate the dynamic list of divs and return it, resulting in it being inserted into wherever you targeted.

The basic functionality is to use one drop down list to create a set of buttons. The number of buttons will be dependent on the drop down which is selected.

So I was thinking that I would do something like this, but cant figure out how to get all the html.div() objects to actually list out from the function call. I can get the function to work if I explicitly return mulitple html.div()s in the return statement, but I cant seem to get the for loop to string the html.div() objects together as I would for strings:

app.layout = html.Div([
    html.Div(id='div-1', children=[
    	dcc.Dropdown(
            id='tab1-yaxis-column',
            options=[{'label': i, 'value': i} for i in available_indicators],
            value=available_indicators[0]
        ),
    ]),
    html.Div(id='div-2', children=[
    	create_divs(total_divs)
	]),
])

@app.callback(
    dash.dependencies.Output('div-2', 'style'),
	[dash.dependencies.Input('div-1', 'n_clicks')])
def update_bottom_div(n_clicks):
	if n_clicks % 2 == 0:
		return {'visibility' : 'hidden'}
	else:
		return {'display' : 'block'}

I am thinking that markup language maybe the way to go, but haven’t yet tried this.

I think you want to take the approach I described in the previous post. You’re nearly there at the moment. The crucial thing is that you need to move the create_divs function call into the callback to make it dynamic. At the moment the layout is being created once with an initial value of total_divs, but then it is never changes.

Something like this, where the callback targets the children of the element that contains the list of divs, and listens for the value of the dropdown.

@app.callback(Output('div-2', 'children'), [Input('tab1-yaxis-column', 'value')])
def update_bottom_div(total_divs):
	return create_divs(total_divs)

Note also that you can now remove the call to create_divs in your layout, as it will be called on the initialization of the app with the initial value of the dropdown.

Thanks, I think I’m nearly understanding how to do this.

The thing that’s still confusing me is how to represent the different number of html.div() elements in the children element of ‘div-2’. Right now the best I can figure it out is to append the div’s in the same list, but to actually create the necessary structure is not clear to me. I need the output to look something like this:

html.Div(id='div-2', children=[
	html.Div(id='id-' + str(i), children=[html.Button('Reset', id='id-0-'+str(i), n_clicks=0)],
	html.Div(id='id-' + str(i), children=[html.Button('Reset', id='id-0-'+str(i), n_clicks=0)]
	... for i < value # of divs
]),

But I cant figure out how to get the variable number of html.div() elements to actually show up in this structure out of the for loop. How do I concatenate them together? Right now the best I can figure it out is to create a list of them, but to actually deconstruct that list in the layout is also not clear.

app.layout = html.Div([
    html.Div(id='div-1', children=[
    	dcc.Dropdown(
            id='tab1-yaxis-column',
            options=[{'label': i, 'value': i} for i in available_indicators],
            value=available_indicators[0]
        ),
    ]),
    html.Div(id='div-2', children=[
    	
	]),
])

@app.callback(
    dash.dependencies.Output('div-2', 'children'),
	[dash.dependencies.Input('tab1-yaxis-column', 'value')])
def update_bottom_div(value):
	i=0
	return_divs = []
	while i < value:
		return_divs.append((html.Div(id='id-' + str(i), children=[html.Button('Reset', id='id-0-'+str(i), n_clicks=0)]))
		i += 1
	return return_divs

As I mentioned, you don’t need to include that list of divs in your initial layout. When the app loads, all callbacks fire once automatically with their initial inputs. So the initially empty children property will be updated with the list of divs returned by the callback. So in your layout, you just need:

html.Div(id='div-2')

Edit:

This part of Dash can be a little confusing. Something that might help reinforce this idea is that if you look at the uses of the Graph() component in the tutorial, you’ll see that when declared in the initial layout, they don’t include a figure property, even though that’s essential to actually rendering a chart. This is because the callbacks target the figure property, and since all callbacks fire on the initial load, it looks for all the world like you did include the figure in your initial layout, but really it was generated by the callback.

1 Like

Thanks! I understand now and fixed the issue by removing the initial layout inclusion.

One thing I also didn’t fully understand was that including a list of Div() objects in the children property would actually render that list as multiple Div()'s. This is why I thought I needed to return concatenated div() objects rather than a list. Regardless, it works now, thank you for the help!

1 Like