Dash button inside of tab not found in callback - python

I have a function that takes two inputs and a button to generate a table. It was working well until I placed the inputs and the button in a tab. Now the function does not recognize the button Id.
I get an error while loading the app. Function "displayDVFiltre" is not able to find component ids "VF_button" and "result". Most likely because they are generated during the "render_content" callback.
informations = html.Div([dcc.Tabs(id="tabs-styled-with-inline", value='VF', children[dcc.Tab(label='Ventes historiques', value='VF', disabled=False)], style=tabs_styles), html.Div(id='tabs-content inline')])
#app.callback(Output('tabs-content-inline', 'children'),
Input('tabs-styled-with-inline', 'value'))
def render_content(tab):
if tab == 'VF':
return VF_tab
VF_tab = dbc.Card(
dbc.CardBody(
[
html.H5("Historique des ventes immobilières"),
dbc.Input(id="cp", placeholder="Code postal", type="text"),
dbc.Input(id="cd", placeholder="Code cadastral", type="text"),
dbc.Button("Voir les ventes", id="VF_button", color="dark"),
html.Div(id="result"),
]
),
className="mt-3",
style=tab_style_inside
)
#app.callback(
Output("result", 'children'),
Input("VF_button","n_clicks"),
State("cp","value"),
State("cd","value")
)
def displayDVFiltre(click, codePostal, codeCadastral):
if click:
df = vf.valeurFonciereParCPetCodeCadastal(codePostal, codeCadastral)
print(df.columns)
return dash_table.DataTable(df.to_dict('records'), [{"name": i, "id": i} for i in df.columns])
else:
return {}

Related

How to update a layout into a Python Dash application

i'm new with Python & Dash and i don't know how to force my application to refresh the shown layout without use the callback; this is my code:
AppMain.py
app.layout = html.Div(
children=[
dcc.Location(
id=ID_URL,
refresh=False
),
html.Div(
[
dcc.Link(
'Test',
href=HREF_PAGE_TEST,
),
dcc.Link(
'Single',
href=HREF_PAGE_SINGLE,
),
dcc.Link(
'Common',
href=HREF_PAGE_COMMON,
),
]
),
html.Div(
id=ID_PAGE_CONTAINER,
),
),
#app.callback(
Output(ID_PAGE_CONTAINER, 'children'),
[Input(ID_URL, 'pathname')],
)
def clickOnPageToShow(pathname):
return findPageToShow(pathname)
def findPageToShow(pathname):
if pathname == HREF_PAGE_TEST:
PageTest.data_frame = shared_data_frame
return PageTest.layout
elif pathname == HREF_PAGE_SINGLE:
PageSingle.data_frame = shared_data_frame
return PageSingle.layout
elif pathname == HREF_PAGE_COMMON:
PageCommon.data_frame = shared_data_frame
return PageCommon.layout
return ""
Every page builds own layout and user choose which one shown clicking on the related dcc.Link; this procedure works fine and user can change page avery time he wants.
What i want now is to build and shown the PageSingle in programmatically way, so i tried to add:
AppMain.showPageSingle()
and
def showPageSingle():
page = findPageToShow(HREF_PAGE_SINGLE)
app.layout[ID_PAGE_CONTAINER].children = [page]
#app.layout[ID_PAGE_CONTAINER].children = page
But nothing happens.
What i do wrong?
Thanks
EDIT
How suggested i use a callback raised when use click on a cell of table created into page Common
PageCommon.py
#app.callback(
Output(ID_PAGE_CONTAINER, 'children'),
[
Input(ID_TABLE_COMMON, 'active_cell'),
Input(ID_TABLE_COMMON, 'data')
]
)
def clickOnCell(active_cell, data_table):
if active_cell is not None:
row_index = active_cell['row']
col_index = active_cell['column']
import AppMain
return AppMain.buildPageSingle()
return []
BUT in this case, application doesn't respond when i choose a page, maybe because the callback is not valid.
You can add an "interval" component in this way (in your layout):
dcc.Interval(
id='interval-component',
interval=60 * 1000, # in milliseconds , 20 seconds every refresh
n_intervals=0
And then, you can refer to that component.
#app.callback(Output('live-update-time', 'children'),
[Input('interval-component', 'n_intervals')])
def update_time(n):
current_time = datetime.datetime.now()
return html.P(f"Last Update at {current_time} ")
In the above example, i refresh my page every 20 second.

Plotly Dash Callback error : Trying to hide Div components

I am trying to hide input components Div after link is clicked.
I have given the Div id='input_fields', so that i can hide it's child components but on return app1.layout, {'display': 'none'} I get the following error.
"Callback error updating display-page.children, input_fields.children"
Class
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Br(),
html.H1("Interpretation of Financial Statements", style={'text-align': 'center'}),
html.H1("10 year data analysis", style={'text-align': 'center'}),
html.Div([dcc.Input(id='input-1-state', type='text', placeholder="Enter Company Ticker", value=''),
dcc.Input(id='input-2-state', type='text', placeholder="Enter Company Name", value=''),
dcc.Link(' Get Analytics ', href='/apps/app1')], id='input_fields'),
html.Div(id='display-page'),
], style={"margin-left": "5%", "margin-right": "5%"})
#app.callback(Output('display-page', 'children'),
Output('input_fields', 'children'),
[Input('url', 'pathname')],
Input(component_id='input-1-state', component_property='value'),
Input(component_id='input-2-state', component_property='value'))
def display_page(pathname, ticker, company):
if pathname == '/apps/app1':
# generate links
linkGenerator.generateLinks(ticker, company)
# starting crawler
startingCrawlerClass()
return app1.layout, {'display': 'none'}
else:
return ''
The problem is the return statement of the else clause in your callback. Your callback expects two callback outpus, but you're returning one (i.e. the single empty string).
If you run the app in debug mode Dash will tell you this and also what it wants you to return instead:
dash.exceptions.InvalidCallbackReturnValue: The callback ..display-page.children...input_fields.children.. is a multi-output.
Expected the output type to be a list or tuple but got: ''.
So you could do something like this instead:
#app.callback(
Output("display-page", "children"),
Output("input_fields", "style"),
[Input("url", "pathname")],
Input(component_id="input-1-state", component_property="value"),
Input(component_id="input-2-state", component_property="value"),
)
def display_page(pathname, ticker, company):
if pathname == "/apps/app1":
return app1.layout, {"display": "none"}
else:
return '', {'display': 'block'}
So the above example returns a tuple of two elements. Each element correspons to a callback output. Also the output of the input-field is style

Return ‘href’ value on a click event in Python Dash

I have been trying to return the ‘href’ value from click event in python-Dash application. Below is my code snippet:
import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash(
__name__,
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)
def make_table(df, val):
table = []
for index, row in df.iterrows():
rows = []
html.Td([
html.Div([row["col1"]]),
html.A(id = 'link',href=row["file-link"], children=row["link-name"], target="_blank"),
])
table.append(html.Tr(rows))
return table
app.layout = html.Div([
html.Table(
id="table-element",
className="table__container",
)
],
className="six columns",
),
#app.callback(
Output("link", 'pathname'),
[Input('link', 'n_clicks')],
[State('link', 'href')]
)
def open_link(n_clicks, href):
enable_open_link(href) #enable_open_link function takes in the href string value (a local filesystem link) and opens it up in a new window.
#app.callback(
Output("table-element", 'children'),
[Input("input-1-submit", 'n_submit')],
[State('input-1-submit', 'value')]
)
def update_output(ns1,val):
table = make_table(df,val)
return table
This code works upto some extent i.e. it does returns a href value, but not the one I click. It always returns the last href value stored inside the html table.
Is there a way to fetch the href value when I click on the link?
I know I can use Jquery to fetch the correct href value… but I didn’t find a way to integrate javascript within the call-back function.
I think it's because all the links you'r creating have the same id ='link'.
You'll need to find a way around that. One possibilty would be to generate the id when creating it based on the index of your df, but then you'll also have to create the corresponding callbacks. This thread tells you how it could be done. plotly dash: create multiple callbacks (with loop?)
Taking a clue from thread provided by #Rudertier, I was able to get the solution.
Below is the updated code snippet:
def make_table(df, val):
table = []
for index, row in df.iterrows():
rows = []
html.Td([
html.Div([row["col1"]]),
html.A(id = 'link'+str(index),href=row["file-link"], children=row["link-name"], target="_blank"),
])
table.append(html.Tr(rows))
return table
app.layout = html.Div([
html.Table(
id="table-element",
className="table__container",
)
],
className="six columns",
),
links = ['link1','link2','link3','link4','link5','link6','link7','link8','link9','link10']
for link in links:
#app.callback(
Output('{}'.format(link), 'pathname'),
[Input('{}'.format(link), 'n_clicks')],
[State('{}'.format(link), 'href')]
)
def open_link(n_clicks, href):
enable_open_link(href) #enable_open_link function takes in the href string value (a local filesystem link) and opens it up in a new window.
#app.callback(
Output("table-element", 'children'),
[Input("input-1-submit", 'n_submit')],
[State('input-1-submit', 'value')]
)
def update_output(ns1,val):
table = make_table(df,val)
return table

Plotly Dash: How to reset the "n_clicks" attribute of a dash-html.button?

I have a basic datatable in plotly/dash. My goal is to upload (or print for the sake of the example...) after I press the upload-button.
The issue is, that I can't figure out how to get the n_clicks attribute of the button back to zero. So what happens is that after I clicked the button for the first time it prints continuously whenever something changes (row added or number changed/added), but what I want is for it to print only once whenever I click the button.
This is the code:
import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_daq as daq
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
df = pd.read_csv('.../dummy_csv.csv')
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div(id='my-div'),
dash_table.DataTable(
id='adding-rows-table',
style_data={
'whiteSpace': 'normal',
'height': 'auto'
},
style_table={
'maxHeight': '800px'
, 'overflowY': 'scroll'
},
columns=[
{'name': i, 'id': i} for i in df.columns
],
data=df.to_dict('records'),
editable=True,
row_deletable=True
),
html.Button('+ Row', id='editing-rows-button', n_clicks=0),
html.Button('Update', id='btn-nclicks-1', n_clicks=0 ),
])
#app.callback(
Output(component_id='my-div', component_property='children'),
[Input('btn-nclicks-1', 'n_clicks'), Input('adding-rows-table', 'data')]
)
def update_output_div(n_clicks, data):
if n_clicks > 0:
print(data)
# n_clicks = 0
# return n_clicks
else:
print("else loop")
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, rows, columns):
if n_clicks > 0:
rows.append({c['id']: '' for c in columns})
return rows
if __name__ == '__main__':
app.run_server(debug=True)
This the CSV:
a,b,c,d
1,1,5,1
2,2,5,1
2,33,6,2
3,4,6,2
And this is the "faulty" output.
You could use the dash.callback_context property to trigger the callback only when the number of clicks has changed rather than after the first click. See the section on "Determining which Button Changed with callback_context" in the Dash documentation. The following is an example of how you could update your callback.
#app.callback(Output(component_id='my-div', component_property='children'),
[Input('btn-nclicks-1', 'n_clicks'), Input('adding-rows-table', 'data')])
def update_output_div(n_clicks, data):
changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
if 'btn-nclicks-1' in changed_id:
print(data)
# n_clicks = 0
# return n_clicks
else:
print("else loop")
You can reset the n_clicks=None attribute, and it won't trigger an infinite loop when used in combination with a PreventUpdate null condition that you are probably already using.
#callback(
Output('some_btn', 'n_clicks'),
Input('some_btn', 'n_clicks')
)
def upon_click(n_clicks):
if (n_clicks is None): raise PreventUpdate
print(n_clicks)#1
return None
It's a bit more explicit and faster than callback_context 🤷‍♂️ For example, my button's id contained a complex "pattern-matching callback" so I didn't want to parse the callback context for that id
An option that worked for me is: n_clicks = 0 in the button declaration.
create list and add click data, then check if next index is greater than current index.
An approximate example with your code would be:
list_clicks = []
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, rows, columns):
list_clicks.append(n_clicks)
for index in range(len(list_clicks)-1):
if list_clicks[index + 1] > list_clicks[index]:
list_clicks.clear()
rows.append({c['id']: '' for c in columns})
return rows
clicking on the button n_clicks is increased by 1.
What the code block does is compare if the current index is greater than the previous one.
If it is greater, it means that the button was clicked.
list_clicks.clear() it is to clean the list and not iterate over the same passed values.
maybe you could adapt it better to your code!

Python plotly Dash updating/refreshing layout

I have app that imports layout from second module then renders it in tab as returning app.layout to this tab content. I have problem with thing that layout is imported with default values of inputs and when I have inputs filled with keys and change between tabs then when I click back to the inputs tab the values reset to default 'x'. I managed to solve this problem by declaring layout second time in a callback but it doesn't look good and would require to write layout 2 times for further functionalities. There is my code:
if 'manager' not in vars():
manager = Recognition('xx', 'xx')
print('defining manager')
lcheck = 0
while lcheck == 0:
layout = [
html.Div([
html.Div(
[
html.H3("Primary API key"),
dcc.Input(
id='primary-key',
value=manager.api_key,
placeholder='Enter a value...',
type='text',
style = {"width":"50%"}
),
html.H3("Private API key"),
dcc.Input(
id='private-key',
value=manager.api_secret,
placeholder='Enter a value...',
type='text',
style = {"width":"50%"}
),
],
),
html.Button('Submit', id='button', className='btn btn-primary'),
html.Div(id='output-hidden')
])
]
lcheck=1
#app.callback(
Output('primary-key', 'value'),
[Input('button', 'n_clicks')],
[State('primary-key', 'value'),
State('private-key', 'value')]
)
def update_output(n_clicks, value1, value2):
values = {'value1':value1, 'value2':value2}
global manager
global layout
manager.update(value1, value2)
layout = [
html.Div([
html.Div(
[
html.H3("Primary API key"),
dcc.Input(
id='primary-key',
value=manager.api_key,
placeholder='Enter a value...',
type='text',
style = {"width":"50%"}
),
html.H3("Private API key"),
dcc.Input(
id='private-key',
value=manager.api_secret,
placeholder='Enter a value...',
type='text',
style = {"width":"50%"}
),
],
),
html.Button('Submit', id='button', className='btn btn-primary'),
html.Div(id='output-hidden')
])
]
lcheck=0
return values['value1']
As you can see it saves new layout everytime I send new data with button but this solution looks terrible I would like to find better solution without chaos in code. Whole thing is to update value=manager.api_key, and value=manager.api_secret, in layout variable. I was thinking to get access of the values like from normal list but it is not possible since it is not nested as regular list. Is there any way to access the values for example layout.getid("primary-key").value or something like this? How to clean this code?
I have figured out how to solve this problem. As my app us rendering app.layout everytime I select a tab, so it always render the layout that was assigned during first import. Firstly I wanted to override layout everytime in a callback function but it has no sense to write layout= 2 times. So I found in dash docs a dash core component named dcc.Store (it stores json in hidden div). I used it like this:
html.Div([
dcc.Store(id='storage')
],
id="hiddendata",
style={"display": "none"},
),
This div is placed directly after 'Tabs' div and is there as a 'cache' memory for tabs to remember previous state. And finally I have updated callbacks to change data in dcc.Store everytime I send new keys so when I leave the tab, other tabs know what is there:
#app.callback(
Output('storage', 'data'),
[Input('button', 'n_clicks')],
[State('primary-key', 'value'),
State('private-key', 'value')]
)
def update_output(n_clicks, value1, value2):
values = {'value1':value1, 'value2':value2}
global manager
global layout
manager.update(value1, value2)
lcheck=0
return values
#app.callback(
Output('primary-key', 'value'),
[Input('storage', 'data')],
[State('storage', 'data')])
def update_values(values, value):
if values:
return value['value1']
#app.callback(
Output('private-key', 'value'),
[Input('storage', 'data')],
[State('storage', 'data')])
def update_values(values, value):
if values:
return value['value2']

Categories