More sliders! (help with button callback example)

I want to make a button that adds a slider every time. This code works for adding exactly one slider and then just updates that one. Can someone help me modify this example? I need more sliders!!!

import dash
from dash.dependencies import State, Event, Input, Output
import dash_core_components as dcc
import dash_html_components as html
import numpy as np

app = dash.Dash()

app.layout = html.Div([
	html.H1('MORE SLIDERS!!!'),
	html.Button('Make One More', id='my-button-events-example'),
	html.Div(id='counter'),
	html.Div(id='0'),
	dcc.Slider(
	    min=0,
	    max=90,
	    value=np.random.randint(0,90),
	)

])	

@app.callback(
    Output(component_id='counter', component_property='children'),
    events=[Event('my-button-events-example', 'click')],
)

def test():
	return dcc.Slider(
	    min=0,
	    max=90,
	    value=np.random.randint(0,90),
	)

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

You can to add sliders to a children property of one of your Divs (in a loop, if that is what you need).
The only complication is that you need to predefine all Callbacks for all slides you are ever going to create.

Tip: you can create callbacks programatically, if you want to create callbacks in a loop too. But, again, this should be executed as part of initialisation, not inside another callback.

3 Likes

Note that the n_clicks property has been added to all of the html components, so you should use this property instead of the Events now:

import dash_html_components as html

app.layout = html.Div([
    html.Button('Add a slider', id='button', n_clicks=0),
    html.Div(id='slider-container'),
])

@app.callback(Output('slider-container', 'children'), [Input('button', 'n_clicks')])
def add_sliders(n_clicks):
    return html.Div(
        [dcc.Slider(id='slider-{}'.format(i)) for i in range(n_clicks)] + 
        [html.Div(id='output-{}'.format(i)) for i in range(n_clicks)]
    )

# up to 10 sliders
for i in range(10):
    @app.callback(Output('slider-{}'.format(i), 'children'), [Input('slider-{}'.format(i), 'value')])
    def update_output(slider_i_value_):
        return slider_i_vlaue

There are a few limitations with this approach right now:

  • You have to define all of your callbacks in advance. If your UI can support 100 sliders, then you need to define 100 callbacks
  • A single callback’s Output will only work if all of the Inputs are rendered. So, you can’t have an output element that depends on a variable number of visible Input elements.

This behaviour will likely update in the future and allow for more sophisticated dynamic interfaces. It’s helpful to know what types of interfaces users are attempting to design, so thanks for posting this issue. Any more info on what you’re trying to build and why would be super helpful :slight_smile:

2 Likes

thanks for the help! unfortunately i’m getting an error running your example:

Traceback (most recent call last):
File “many_sliders.py”, line 23, in
@app.callback(Output(‘slider-{}’.format(i), ‘children’), [Input(‘slider-{}’.format(i), ‘value’)])
File “/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/dash/dash.py”, line 528, in callback
self._validate_callback(output, inputs, state, events)
File “/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/dash/dash.py”, line 441, in _validate_callback
).replace(’ ', ‘’))
dash.exceptions.NonExistantIdException:
Attempting to assign a callback to the
component with the id “slider-0” but no
components with id “slider-0” exist in the
app’s layout.
Here is a list of IDs in layout:
[‘button’, ‘slider-container’]
If you are assigning callbacks to components
that are generated by other callbacks
(and therefore not in the initial layout), then
you can suppress this exception by setting
app.config.supress_callback_exceptions=True.

I made sure to update dash and all of the components before running…

Ah yeah, sorry, I left out that part. You need to add:

app.config.supress_callback_exceptions=True

to make it work. The exception printed that out:

but that’s a little confusing.

We throw an exception if an ID wasn’t found in the initial app.layout because it’s easy to make a typo and mispell an ID. In this case, we’re generating elements directly (not in the layout) but defining our callbacks all upfront. Dash throws an exception in case that we mispelled something. In this case we didn’t, so we can just supress the exception.

No errors now, but still not working. Every time I click ‘add a slider’ i see this in the terminal:

127.0.0.1 - - [24/Jul/2017 12:09:40] “POST /_dash-update-component HTTP/1.1” 200 -

But no sliders appear :confused:

Can anyone else get @chriddyp 's code to work?

@chriddyp - any help you can offer here?

Ah yeah, Dash is picking up the correct JS and CSS bundles. Here’s an updated example:

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

app = dash.Dash()

app.config.supress_callback_exceptions = True

app.layout = html.Div([
    html.Button('Add a slider', id='button', n_clicks=0),
    html.Div(id='slider-container'),

    # this is a hack: include a hidden dcc component so that
    # dash registers and serve's this component's JS and CSS
    # libraries
    dcc.Input(style={'display': 'none'})
])

@app.callback(Output('slider-container', 'children'), [Input('button', 'n_clicks')])
def add_sliders(n_clicks):
    return html.Div(
        [html.Div([
		html.Div(dcc.Slider(id='slider-{}'.format(i))),
	        html.Div(id='output-{}'.format(i), style={'marginTop': 30})
	]) for i in range(n_clicks)]
    )

# up to 10 sliders
for i in range(10):
    @app.callback(Output('slider-{}'.format(i), 'children'), [Input('slider-{}'.format(i), 'value')])
    def update_output(slider_i_value):
        return slider_i_value

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

@chriddyp Not sure it it’s intentional but supress_callback_exceptions is misspelled, should be suppress_callback_exceptions?

:man_facepalming:it’s misspelled in the code as well

5 Likes

I happened to scroll back to this post and now I’m wondering if this is still in the plans. The types of interfaces I have in mind are:

  • user loads some data from a file or database
  • for each item in the data, app creates an interactive component. Say, a graph + a dropdown that controls something about the graph.

Right now the easiest way to accomplish this seems to me to be a fixed maximum number of items, and then unhide the relevant number when the data is loaded. Are there better ideas?

I have a similar issue, in that our application have no upper limit on how many UI elements a user can add. If callbacks can be generated automatically (in a loop) that would be really really awesome.