I have been working on a menu where you can select a target audience using a number of drop-downs, checklists and sliders. Some of these components use callbacks to update the selectable values in other components, while others are independent from it all.
The problem however is that when a callback does trigger all previous made choices (selections in drop-downs and checklists) get cleared. It seems to be a bug since only using non-callback components it all works as intended and the values remain. Though I must admit I could always be the case of overlooking a very simple mistake on my end.
I left out some of the lists I was using to fill the selections trying to shorten the text for this post, but I can state the values are loaded correctly in the components, its jus that they get cleared when another component triggers a callback. Additionally the layout is returned to a simple page selector:
app.layout = html.Div([
dcc.Location(id=āurlā, refresh=False),
html.Div(id=āpage-contentā)
])
@app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/':
return home()
elif pathname == '/test':
return test()
elif pathname == '/audience':
return audience_selection()
else:
return '404'
if __name__ == '__main__':
app.run_server(debug=True)
Now, my apologies for the following lengthy, possibly unreadable, script:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, State, Output
from app import app
app.config.supress_callback_exceptions = True
COLOURS = {'One':'#172A39', 'Two':'#EFEFEF'}
def audience_selection():
"""Target audience selection layout"""
layout = html.Div([
html.Div([
html.H2('Create your target audience', className='eight columns', style={'color':COLOURS['Two']}),
html.Button('Load', id='load_group', className='two columns',
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two'], 'margin-top':'10'}),
html.Button('Save', id='save_group', className='two columns',
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two'], 'margin-top':'10'}),
html.Div([], id='load_container', className='twelve columns'),
html.Div(className='twelve columns', style={'margin-top':'8', 'border-bottom':'2px solid white'}),
], id='header', className='twelve columns', style={'height':'100%', 'margin-top':'0'}),
html.Div([
html.H4('- Device', className='twelve columns', style={'color':COLOURS['Two']}),
html.Button('Desktop', id='desktop_input', className='four columns', n_clicks=0,
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two']}),
html.Button('Tablet', id='tablet_input', className='four columns', n_clicks=0,
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two']}),
html.Button('SmartPhone', id='phone_input', className='four columns', n_clicks=0,
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two']}),
], id='devices', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Gender', className='twelve columns', style={'color':COLOURS['Two']}),
dcc.Dropdown(id='gender_input',
placeholder='Select gender(s)...',
options=[
{'label':'Male', 'value':'m'},
{'label':'Female', 'value':'f'},
{'label':'Other', 'value':'o'}],
multi=True, value=''
),
], id='gender', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Location', className='twelve columns', style={'color':COLOURS['Two']}),
dcc.Dropdown(id='location_input_provence',
placeholder='Select provence(s)...',
options=[
{'label':'Groningen', 'value':'gr'},
{'label':'Friesland', 'value':'fr'},
{'label':'Drente', 'value':'dr'}],
multi=True, value=''
),
dcc.Dropdown(id='location_input_town',
placeholder='Select town(s)...',
options=[
{'label':'One', 'value':'1'},
{'label':'Two', 'value':'2'},
{'label':'Three', 'value':'3'}],
multi=True, value=''
),
], id='location', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Age', className='twelve columns', style={'color':COLOURS['Two']}),
html.H5('Age ranging from: 18 - 80', id='age_output',
className='twelve columns', style={'text-align': 'center', 'color':COLOURS['Two']}),
dcc.RangeSlider(id='age_input', className='twelve columns',
count=1, min=18, max=80, step=1, value=[24,32]),
], id='age', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Products', className='twelve columns', style={'color':COLOURS['Two']}),
dcc.Dropdown(id='product_input', className='twelve columns',
placeholder='Select product(s)...',
options=gen_options(products),
multi=True, value=''
),
html.Div([], id='product_output', className='twelve columns', style={'margin-top':'5'}),
], id='products', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Platforms', className='twelve columns', style={'color':COLOURS['Two']}),
html.Div([
dcc.Checklist( id='device_input', labelStyle={'display':'inline-block', 'color':COLOURS['Two']},
options=gen_options(platforms), values=[],
),
], className='twelve columns', style={'color':COLOURS['Two']}),
], id='platforms', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.H4('- Interests', className='twelve columns', style={'color':COLOURS['Two']}),
dcc.Dropdown(id='interests_input',
placeholder='Select interest(s)...',
options=gen_options(interests),
multi=True, value=''
),
], id='interests', className='twelve columns', style={'height':'100%', 'margin-top':'5'}),
html.Div([
html.Div('[numberUsers] users part of this selection which is [percentageUsers]%, and have an average age of [AverageAge]', className='twelve columns', style={'color':COLOURS['Two']}),
], id='footer', className='twelve columns', style={'height':'100%', 'margin-top':'25'}),
], className='background', style={'backgroundColor':COLOURS['One'], 'padding':'5', 'width':'600', 'float':'right', 'overflow-y':'auto'})
return layout
@app.callback(
Output('load_container', 'children'),
[Input('load_group', 'n_clicks')])
def load_audience(n_clicks):
return html.Div([
html.H5('Select your saved target audience', className='eleven columns',
style={'color':COLOURS['Two'], 'float':'right'}),
html.Div([
dcc.Dropdown(id='loading_input', placeholder='Select which target audience to load...',
options=[
{'label':'One', 'value':'1'},
{'label':'Two', 'value':'2'},
{'label':'Three', 'value':'3'}],
value='1'),
], className='nine columns'),
html.Button('Load', id='load_selected', className='two columns',
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two'], 'float':'right'}),
], className='twelve columns')
for name in ['desktop_input', 'tablet_input', 'phone_input']:
@app.callback(
Output('{}'.format(name), 'style'),
[Input('{}'.format(name), 'n_clicks')])
def update_device_input(n_clicks):
if n_clicks % 2 == 0:
return {'backgroundColor':COLOURS['One'], 'color':COLOURS['Two']}
else:
return {'backgroundColor':COLOURS['Two'], 'color':COLOURS['One']}
@app.callback(
Output('location_input_town', 'options'),
[Input('location_input_provence', 'value')])
def update_town_selection(selection):
towns = []
if len(selection) > 0:
for select in selection:
if select == "gr":
for a in towns_in_gr:
towns.append({'label':a, 'value':a})
elif select == "fr":
for b in towns_in_fr:
towns.append({'label':b, 'value':b})
elif select == "dr":
for c in towns_in_dr:
towns.append({'label':c, 'value':c})
else:
for a in towns_in_gr:
towns.append({'label':a, 'value':a})
for b in towns_in_fr:
towns.append({'label':b, 'value':b})
for c in towns_in_dr:
towns.append({'label':c, 'value':c})
return towns
@app.callback(
Output('age_output', 'children'),
[Input('age_input', 'value')])
def update_age_feedback(values):
return "Age ranging from: {} - {}".format(values[0], values[1])
@app.callback(
Output('product_output', 'children'),
[Input('product_input', 'value')])
def update_subs_selection(selection):
accounts = []
if len(selection) < 1:
return html.Div('Selecting one or more products will show the available subscriptions here',
style={'color':COLOURS['Two']})
for select in selection:
for product in products:
if select == product:
if account_types[product] == 'subs_long':
for name in account_long:
accounts.append('{} {}'.format(select, name))
elif account_types[product] == 'subs_short':
for name in accounts_short:
accounts.append('{} {}'.format(select, name))
break
return dcc.Checklist( id='account_output', labelStyle={'display':'inline-block', 'color':COLOURS['Two']},
options=gen_options(accounts), values=[],
)
def gen_platform_input():
return html.Div([html.Button('{}'.format(name), id='{}'.format(name), n_clicks=0,
style={'backgroundColor':COLOURS['One'], 'color':COLOURS['Two'], 'borderColor':COLOURS['Two'],
'margin-right':'5', 'display':'inline-block'})
for name in platforms])
for name in platforms:
@app.callback(
Output('{}'.format(name), 'style'),
[Input('{}'.format(name), 'n_clicks')])
def update_plaform_input(n_clicks):
if n_clicks % 2 == 0:
return {'backgroundColor':COLOURS['One'], 'color':COLOURS['Two'], 'borderColor':COLOURS['Two'], 'margin-right':'5', 'display':'inline-block'}
else:
return {'backgroundColor':COLOURS['Two'], 'color':COLOURS['One'], 'borderColor':COLOURS['Two'], 'margin-right':'5', 'display':'inline-block'}
def gen_options(list_of_options):
out = []
for item in list_of_options:
out.append({'label':'{}'.format(item), 'value':'{}'.format(item)})
return out