Issue with my dynamic hyperlink button

Hi, I’m trying to add a button to a dashboard that will open a different webpage based on user input. I can get the button to generate, but the link doesn’t appear to update when input is provided by the user.

import dash_ag_grid as dag
#import dash_table
from dash import dash_table
import dash
from dash.dependencies import Input, Output
from dash import dcc
from dash import html
import plotly.express as px
import dash_bootstrap_components as dbc
import flask
import pandas as pd
import time
import os

server = flask.Flask('app')
server.secret_key = os.environ.get('secret_key', 'secret')

app = dash.Dash('app', server=server,external_stylesheets=[dbc.themes.VAPOR], url_base_pathname='/driver-proxy/o/0/0927-153023-a9142vm9/9999/')
app.layout = html.Div(children=[
    # All elements from the top of the page
    dcc.Input(
        id='pdn-input',
        type='text',
        placeholder='Enter a PDN',
        value=''
    ),
#sp_df is a pandas dataframe, converting to json to be passed into callback
    dcc.Store(id="sp-links", data=json.loads(sp_df.to_json(orient = 'records'))),
    html.Br(),
               dbc.Button(
                id='dynamic-link',
                children="Search",
                color="primary",
                className="ms-2",
                n_clicks=0,
                href="",
                target="_blank"
            ),
   
      
])

@app.callback(
    Output('dynamic-link', 'href'),
    Input('pdn-input', 'value'),
    Input('sp-links','value')
)
def update_link(pdn_input,sp_links):
    # If input is empty, return a default link
    if not pdn_input:
      return ''
    # Otherwise, return the user-provided input as the link
    return [row for row in sp_links if row.get('pdn') == pdn_input][0]['url']

if __name__ == '__main__':
    # Define run_server port with internal port value from setup.sh
    app.run(port=8060)

Hey @PlottingPlotly how does the user provide the link? The way you are using the dcc.Input() as Input in your callback leads the callback to be triggered at each keyboard stroke. Is that intended?

The application gets the link when the user provides a different related output. They are in a pandas dataframe.

And no, it’s not intended to be that way. If it is causing or could cause a problem, how could it be resolved?

Hey @PlottingPlotly it’s not necessarily a problem.

Here an example on how to do this:

import dash
from dash import Input, Output, no_update
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
import pandas as pd
import json

# dummy data
data = {
    'pdn': ['uhncr', 'plotly', 'amnesty'],
    'url': ['https://www.unhcr.org', 'https://www.plotly.com', 'https://www.amnesty.org/']
}

# Create DataFrame
sp_df = pd.DataFrame(data)

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div(children=[
    # All elements from the top of the page
    dcc.Input(
        id='pdn-input',
        type='text',
        placeholder='Enter a PDN',
        value=''
    ),
    # sp_df is a pandas dataframe, converting to json to be passed into callback
    dcc.Store(id="sp-links", data=json.loads(sp_df.to_json(orient = 'records'))),
    html.Br(),
    dbc.Button(
        id='dynamic-link',
        children="Search",
        color="primary",
        className="ms-2",
        n_clicks=0,
        href="",
        target="_blank"
    ),

])


@app.callback(
    Output('dynamic-link', 'href'),
    Input('pdn-input', 'value'),
    Input('dynamic-link', 'n_clicks'),
)
def update_link(pdn_input, sp_links):
    # If input is empty, return a default link
    if not pdn_input:
        return "https://duckduckgo.com/"

    # Otherwise, return the user-provided input as the link
    filtered = sp_df.loc[sp_df['pdn'] == pdn_input, 'url'].values
    if filtered.size > 0:
        return filtered[0]

    return no_update

if __name__ == '__main__':
    # Define run_server port with internal port value from setup.sh
    app.run(debug=True, port=8060)

Thank you @AIMPED

When I run this isolated code it works great, however when I integrate it into the rest of my code the button (though generated) no longer functions. Below is a truncated version of my code leaving off the dataframe generation. When I put in my input, my console is outputting a key error. This is related to pdn_df = pdn_data[pdn_input][table_name] which is part of a different callback, but the visualizations related to that are operating fine.

import dash_ag_grid as dag
#import dash_table
from dash import dash_table
import dash
from dash.dependencies import Input, Output, State
from dash import dcc
from dash import html
import plotly.express as px
import dash_bootstrap_components as dbc
import flask
import pandas as pd
import time
import os
import pandas as pd
import plotly.express as px
from plotly import graph_objects as go
import random

server = flask.Flask('app')
server.secret_key = os.environ.get('secret_key', 'secret')

# Define app object with url_base_pathname value from setup.sh
app = dash.Dash('app', server=server,external_stylesheets=[dbc.themes.VAPOR], url_base_pathname='/driver-proxy/o/0/0927-153023-a9142vm9/9999/')

app.layout = html.Div(children=[
    # All elements from the top of the page
     html.H1("My PDN KPI Finder"),
  
    dcc.Input(
        id='pdn-input',
        type='text',
        placeholder='Enter a PDN',
        value=''
    ),
    dcc.Store(id="sp-links", data=json.loads(sp_df.to_json(orient = 'records'))),
    html.Br(),
        
   

    dbc.Row([
      dbc.Col([    html.Label("Select a table:"),
    dcc.Dropdown(
        id='table-dropdown',
        options=tables,
        value=None, # Start with no nutrient selected, or choose a default like 'calories'
        placeholder="Select a table",
        clearable=False # If you want the user to always have one selected after first choice
    ),
    html.Div(
                id="table-container",
                className="table-container",
                children=[
                    html.Div(
                        id="table-upper",
                        children=[
                            html.P("PDN Data"),
                            dcc.Loading(children=html.Div(id='pdn-container')),
                        ],
                    )
                        ],
                    )]),
     dbc.Col([  html.Br(),html.Br(),html.Br(),
           html.Div(
                id="table-container",
                className="table-container",
                children=[
                    html.Div(
                        id="table-upper",
                        children=[
                            html.P("Error Data"),
                            dcc.Loading(children=html.Div(id='error-container')),
                        ],
                    ),])]),
                        
              dbc.Col([       html.Br(), 
               dbc.Button(
                id='dynamic-link',
                children="Search",
                color="primary",
                className="ms-2",
                n_clicks=0,
                href="",
                target="_blank"
            )   
        ,
         html.Div(id='dynamic-link'),
         html.Br(), 
          html.Div(
                id="table-container",
                className="table-container",
                children=[
                    html.Div(
                        id="table-upper",
                        children=[
                            html.P("Row Count Data"),
                            dcc.Loading(children=html.Div(id='count-container')),
                        ],
                    )
                        ],
                    ),])]),
  dbc.Row([
    # New Div for all elements in the new 'row' of the page
   dbc.Col(dcc.Graph(id = 'graph1',figure = plot,style={'width':'90%','height':'40vh'})),
   dbc.Col(dcc.Graph(id='graph2',figure=fig, style={'width': '90%','height':'40vh'}))
  ]),
])
@app.callback(
   
    Output("count-container","children"),
    Output("error-container","children"),
    Input('pdn-input', 'value')
    
)
def update_count_datatables(pdn_input):
  

  count_df = pdn_data[pdn_input]['counts']
  count_data = count_df.to_dict("row1")
  errors_df = pdn_data[pdn_input]['errors']
  error_data = count_df.to_dict("row2")
  return dag.AgGrid(
    rowData=count_df.to_dict("records"),
    columnDefs=[{"field": i} for i in count_df.columns],
  ),  dag.AgGrid(
    rowData=errors_df.to_dict("records"),
    columnDefs=[{"field": i} for i in errors_df.columns],
  )   
@app.callback(
    Output("pdn-container", "children"),   
    Input('pdn-input', 'value'),
    Input('table-dropdown', 'value') # New input
    
)
def update_pdn_datatable(pdn_input, table_name):
  
  pdn_df = pdn_data[pdn_input][table_name]     
  data = pdn_df.to_dict("rows")
 
  
  return dag.AgGrid(
    rowData=pdn_df.to_dict("records"),
    columnDefs=[{"field": i} for i in pdn_df.columns],
  )
@app.callback(
    Output('dynamic-link', 'href'),
    State('pdn-input', 'value'),
    Input('sp-links', 'n_clicks'),
)
def update_link(pdn_input, sp_links):
    # If input is empty, return a default link
    if not pdn_input:
        return ""

    # Otherwise, return the user-provided input as the link
    filtered = [url for url in sp_links if url["pdn"] == pdn_input][0]['url']
    if filtered.size > 0:
        return filtered[0]


if __name__ == '__main__':
    # Define run_server port with internal port value from setup.sh
    app.run(port=8060)

Any idea what code be going wrong?

I did not check all of your code, but I think you need to use Input instead of State here.