Black Lives Matter. Please consider donating to Black Girls Code today.

Problem when combining Dash buttons and tabs

Hi,

First of all thanks for a great product and a great community.
I am building a Dash app in which I want to combine elements (dropdowns, input etc) in tabs with a button that adds rows to a table.
However in order to realise it I hit a couple of issues and can’t get around them easily.

Each tab has a different set of dropdowns, inputs etc and based on what is filled in on a button press it should be added to a table.
The general process works however, my first idea was to have one button per tab (html.Div) element but that would result in multiple callbacks with the same output (hidden html.Div for json data storage) which is not allowed. My second approach was to add all buttons into one callback (nightmare with all the variables + the buttons did not work). My third approach was to add a single button in the overall layout outside of the tabs section. This however raises an dependency loading error from the State of one of the tab elements. I am not quite sure if there is a straight forward solution to it. My code is below and any help would be appreciated.


import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
from dash.dependencies import Input, Output, State
import pandas as pd

app = dash.Dash()


app.server.secret_key = 'notterriblysecret'
app.config['suppress_callback_exceptions'] = True

app.css.append_css({
    "external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"  # Skeleton
})

scenario = pd.DataFrame(columns=['tick', 'type', 'value'])

app.layout = html.Div([
        html.Div(id='intermediate-value', style={'display': 'none'}),
        html.Div([
            html.Div([
                dcc.Tabs(
                    tabs=[
                        {'label': 'Option 1', 'value': 1},
                        {'label': 'Option 2', 'value': 2},
                    ],
                    value=1,
                    id='tabs',
                    vertical=True
                )], className='two columns'
                     ),
            html.Div("Placeholder", id='tab_output', className='six columns'),
            html.Div([
                dt.DataTable(
                    rows=scenario.to_dict('records'),

                    # optional - sets the order of columns
                    columns=sorted(scenario.columns),

                    row_selectable=True,
                    filterable=True,
                    sortable=True,
                    selected_row_indices=[],
                    id='datatable'
                )
            ], className='four columns')
             ], className='row'),
        # <--------- the solution with adding the button to the overall layout
        html.Div([
            html.Button('Add to scenario', id='add_button3')
        ], className='row')
        ], className='container',
    style={'margin': 'auto', 'maxWidth': '1100px'}
    )


@app.callback(Output('tab_output', 'children'), [Input('tabs', 'value')])
def display_content(value):
    if value == 1:
        return html.Div([
            dcc.Dropdown(
                id='comm_type',
                options=[
                    {'label': 'mutual', 'value': '1'},
                    {'label': 'random', 'value': '2'},
                ],
                value='1'
            ),
            # <----------- addint a button for each tab
            html.Button('Add to scenario', id='add_button')
            ]
                        )
    else:
        return html.Div([
            dcc.Dropdown(
                id='comm_type2',
                options=[
                    {'label': 'mutual', 'value': '1'},
                    {'label': 'random', 'value': '2'},
                ],
                value='1'
            ),
            # <----------- addint a button for each tab
            html.Button('Add to scenario', id='add_button2')
            ]
                        )

# <----------- callback with overall button value
@app.callback(Output('intermediate-value', 'children'),
              [Input('add_button3', 'n_clicks')],
              [State('intermediate-value', 'children'),
               # State('comm_type', 'value'), # <---- the dependency loading error happens when this is uncommented order of the elements created might matter?
               State('tabs', 'value')])
def add_entry(n_clicks, value, comm_type, tab):
    print n_clicks
    if n_clicks > 1:
        pre = pd.read_json(value)
        temp = pd.DataFrame([[1, "C", 1]], columns=['tick', 'type', 'value'])
        post = pd.concat([pre, temp])
        return post.reset_index(drop=True).to_json()
    else:
        temp = pd.concat([scenario, pd.DataFrame([[0, tab, comm_type]],
                                                 columns=['tick', 'type',
                                                          'value'])],
                         )
        return temp.to_json()


# <----------- option with two buttons which is not responsive at all
# @app.callback(Output('intermediate-value', 'children'),
#               [Input('add_button', 'n_clicks'),
#                Input('add_button2', 'n_clicks')],
#               [State('intermediate-value', 'children'),
#                State('comm_type', 'value'),
#                State('tabs', 'value')])
# def add_entry(n_clicks, n_clicks2, value, comm_type, tab):
#     print n_clicks
#     if n_clicks > 1:
#         pre = pd.read_json(value)
#         temp = pd.DataFrame([[1, "C", 1]], columns=['tick', 'type', 'value'])
#         post = pd.concat([pre, temp])
#         return post.reset_index(drop=True).to_json()
#     else:
#         temp = pd.concat([scenario, pd.DataFrame([[0, tab, comm_type]],
#                                                  columns=['tick', 'type',
#                                                           'value'])],
#                          )
#         return temp.to_json()


# <----------- option with a callback per tab results in multiple callbacks hitting the same hidden output layer


@app.callback(Output('datatable', 'rows'),
              [Input('intermediate-value', 'children')])
def update_table(value):
    return pd.read_json(value).to_dict('records')


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

These are the package versions installed:

dash==0.19.0
dash-core-components==0.15.0rc1
dash-html-components==0.8.0
dash-renderer==0.11.1
dash-table-experiments==0.5.0
plotly==2.2.1

It’s my first post in this forum, I hope I provided all the infos needed to get some pointers.

Cheers,
Florian