Atirag
Atirag

Reputation: 1740

Python Dash: Hide a component with one event and make it visible with another created through a callback

Here is an 'almost' working example of something I want to do with Dash. Almost because this code works but is missing a part of what I intend on doing but when I try to implement that other portion I get an error.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
app.config['suppress_callback_exceptions']=True
image = "tmpimg.png"

app.layout = html.Div([
       html.Div([
       html.Div([
           html.Button('Load image', id='load-button'),
           dcc.Upload(
                   id='upload-data',
                   children=html.Button('Upload image', id='upload-button')
           )
       ]),
       html.Div([
           html.Div(id='images-div'),
           html.Div(id='classification-div'),
           html.Div(id='classification-div2')
       ])
       ])
 ])

@app.callback(
   Output(component_id='images-div', component_property='children'),
   [Input('load-button','n_clicks')]
)
def update_output_div_img(n_clicks):
   return html.Div([html.Img(
                           src=app.get_asset_url(image),
                           style={
                                   'width' : '10%',
                                    'cursor': 'pointer'
                                   }
                       )], id='img'
                   )

@app.callback(
   Output(component_id='classification-div', component_property='children'),
   [Input('img','n_clicks')]
)
def update_output_div1(n_clicks):  
   return html.Div([html.H2('Div1')])

@app.callback(Output('classification-div2', 'children'),
             [Input('upload-data', 'contents')])
def update_output_div2(content):
   return html.Div([html.H2('Div2')])

@app.callback(
   Output(component_id='classification-div', component_property='style'),
   [Input('upload-data', 'contents')]
)
def update_style(content):
   if content:
       return {'display':'none'}
   else: return {'display':'inline'}


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

The update_output_div_img will load an image with a callback when you load the page or when you push the button “Load image”. Now after the image is loaded then you can click it and a text Div1 will appear. When you push the upload button to load an image the “Div1” should disappear and only the Div 2 remain. So far so good.

Now when I click on the image again the “Div1” text doesn’t appear because the display was changed to “none”. I want that when I click on the image again the “Div1” text should show up again so I modified the callback for the style of the first div above like this so that it gets triggered when you click on the image and since there is no content I guess it should change the display to inline.

@app.callback(
   Output(component_id='classification-div', component_property='style'),
   [Input('upload-data', 'contents'),
    Input('img','n_clicks')]
)
def update_style(content,n_clicks):
   if content:
       return {'display':'none'}
   else: return {'display':'inline'}

But this triggers an “Error Loading Dependencies” message when I load the webpage. I think the problem happens because the image that is clicked is generated by another callback while the upload component was loaded from the start.

Any ideas how to work around this?

Upvotes: 2

Views: 7498

Answers (1)

npobedina
npobedina

Reputation: 341

I'm not sure if I understood correctly what you are trying to achieve, but here is a fix to your code to avoid the issue with dependencies:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
app.config['suppress_callback_exceptions']=True
image = "tmpimg.png"

app.layout = html.Div([
       html.Div([
       html.Div([
           html.Button('Load image', id='load-button'),
           dcc.Upload(
                   id='upload-data',
                   children=html.Button('Upload image', id='upload-button')
           )
       ]),
       html.Div([
           html.Div([html.Div(id='img')], id='images-div'),
           html.Div(id='classification-div'),
           html.Div(id='classification-div2')
       ])
       ])
 ])

@app.callback(
   Output(component_id='img', component_property='children'),
   [Input('load-button', 'n_clicks')]
)
def update_output_div_img(n_clicks):
    if n_clicks and n_clicks>0:
        return [html.Img(src=app.get_asset_url(image),
                               style={
                                       'width' : '10%',
                                        'cursor': 'pointer'
                                       })]

@app.callback(
   Output(component_id='classification-div', component_property='children'),
   [Input('img','n_clicks')]
)
def update_output_div1(n_clicks):
    if n_clicks and n_clicks>0:
        return [html.H2('Div1')]

@app.callback(Output('classification-div2', 'children'),
             [Input('upload-data', 'contents')])
def update_output_div2(content):
    if content:
        return [html.H2('Div2')]

@app.callback(
   Output(component_id='classification-div', component_property='style'),
   [Input('upload-data', 'contents'),
    Input('img','n_clicks')]
)
def update_style(content, n_clicks):
   if content and n_clicks and n_clicks>0:
       return {'display':'none'}
   else: return {'display':'inline'}


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

Basically, your problem was that you tried to use as Input a component with id=img that is not defined in the app.layout. Also, consider adding checks on n_clicks for all buttons if you don't want those callbacks being triggered when the page loads.

Upvotes: 5

Related Questions