Trigger of callback without explication

Hello all ! :smiley:

I have a problem with my code that I can’t solve despite many attempts. :sweat_smile:

I have a button that allows me to display and undisplay a list of cards.

However, every time the display and undisplay button is clicked, it trigger other callbacks and i don’t know why.

The idea is to be able to match cargo and vessel and store the matching history in a list. However, this problem distorts my result since it adds elements in this list that should not be there.

I really don’t know what I did wrong in my code if you have an idea…

#from apps.layout_builder import *
#from .config import config_content, register_config_callbacks
from flask import Flask
from dash import Dash, dcc, html, Input, Output, callback 
import os.path
import warnings
warnings.filterwarnings(action='ignore')
import dash
import re
import dash_bootstrap_components as dbc
from flask import g
import requests
import dash  # version 1.13.1
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, ALL, State, MATCH, ALLSMALLER
import plotly.express as px
import pandas as pd
import numpy as np
import os
import datetime as dt
from dash import Dash, dash_table, dcc, html, ctx
from dash.dependencies import Input, Output, State
from LDCDataAccessLayerPy import SqlManager, DataLakeManager, SharePointManager
env = os.getenv("FLASK_ENV")
if not env:
    env = "portal"

server = Flask('app')
APP_ROOT = '/'+os.path.basename(os.path.dirname(__file__))+'/'
server.secret_key = os.environ.get('secret_key', 'secret')
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# Global Variable

Historical_click = []

# Text

Title = html.H1(children='Open Book',style={
    'position' : 'relative',
    'textAlign': 'left',
    'top': 0,
    'fontSize':28,
    'font-family':'Georgia',
    'background-color': '#FFFFFF',
    'color': '#212529',
    'border': 1,
    'width':'300px',
    'margin-left': '45%',
    'outline': '0',
    'margin-top':'2%'
    })

# Button

Show_Card = html.Button('Show Cards',id='show_card',style={
    'position':'fixed',
    'z-index' :'1',
    'margin-left': '0%',
    'fontSize':16,
    'font-family':'Georgia',
    'background-color': '#FFFFFF',
    'color': '#212529',
    'border': 'none',
    'left': '0%',
    'outline': '0',
    'margin-top':'6.5%',
     'width' : '7%'
})

Select_vessel1 = html.Button('Select',id={'type':'btn_created', 'index':'0'},style={
    'backgroundColor':'white'
})

Select_Cargo1 = html.Button('Select',id={'type':'btn_created', 'index':'1'},style={
    'backgroundColor':'white'
})

Select_Cargo2 = html.Button('Select',id={'type':'btn_created', 'index':'2'},style={
    'backgroundColor':'white'
})

Matching_Button = html.Button('Match',id='Match',style={
    'backgroundColor':'white',
    'margin-left': '45%',
  
})

card_1 = dbc.Card(
    dbc.CardBody(
        [
            html.H4("Vessel 1", className="card-title"),
            html.H6("Card subtitle", className="card-subtitle"),
            html.P(
                "Some quick example text to build on the card title and make "
                "up the bulk of the card's content.",
                className="card-text",
            ),
            Select_vessel1
           ,
        ]
    ),
    style={"width": "18rem"},
)

card_2 = dbc.Card(
    dbc.CardBody(
        [
            html.H4("Cargo 1", className="card-title"),
            html.H6("Card subtitle", className="card-subtitle"),
            html.P(
                "Some quick example text to build on the card title and make "
                "up the bulk of the card's content.",
                className="card-text",
            ),
            Select_Cargo1
           ,
        ]
    ),
    style={"width": "18rem",
    #'backgroundColor':'yellow'
     },
)

card_3 = dbc.Card(
    dbc.CardBody(
        [
            html.H4("Cargo 2", className="card-title"),
            html.H6("Card subtitle", className="card-subtitle"),
            html.P(
                "Some quick example text to build on the card title and make "
                "up the bulk of the card's content.",
                className="card-text",
            ),
            Select_Cargo2
           ,
        ]
    ),
    style={"width": "18rem",
    #'backgroundColor':'yellow'
     },
)

List_Card = [card_1,card_2,card_3]
# Content 

Support_for_Cards = html.Div([
    html.Div(
        children = List_Card 
        # Position of the element in the Panel 
        ),
    ]
    # Position of the card panel on the Web Page
    ,id='dashboard-interactivity-container-card',style= {
    'backgroundColor':'blue',
    'position': 'absolute',
    'left': '0px',
    'display': 'none',
    'top': '0px',
    'zIndex' :'7',
    'margin-left': '5%',
    'outline': '0',
    'margin-top':'6.5%',
    'height': '70%',
    'width': '80%'
})

# Container 

Container_div = html.Div(
    [   
        Show_Card,
        html.Div(id='dd-output-container',children=[],style= {
    'position': 'fixed',
    'margin-left': '10%',
    'margin-top':'6.5%'
}),
        html.Div(id='dd-output-container2',children=[],style={'display': 'none'}),
        Support_for_Cards,

    ],
    style={
    'position': 'absolute',
    'left': '0px',
    'top': '0px',
    'margin-left': '0%',
    'outline': '0',
    'height': '100%',
    'width': '100%',
    },
    id = 'container-div'
)

# Callback to update list with last clicked button
@callback(
    Output('dd-output-container2','children'),
    Input({'type':'btn_created','index':ALL}, 'n_clicks'),
    State('dd-output-container2','children'),
    prevent_initial_call=True
)
def update_list(current_clicks, previous_clicks) :
    
    trigger = ctx.triggered_id['index']

    # numbre of clicks of last trigger
    trigger_clicks = ctx.triggered[-1]['value']
    #print('button '+str(trigger)+' has been trigered')
    #  we include this to prevent the list from updating when we create a new button (when its number of clicks is 0)
    if trigger_clicks == 0 :
        raise dash.exceptions.PreventUpdate
        
    last_click  = str(trigger)
    print('last click' + str(last_click))
    Historical_click.append(last_click)
    
    return previous_clicks + [last_click]

#CallBack to Match Element
@app.callback(
   Output('dd-output-container', 'children'),
   Input(component_id='Match', component_property='n_clicks'),
   State('dd-output-container2','children'),
   prevent_initial_call=True)
def match_element(val,liste_button_clicked):
    lists = [[]]
    
    for i in Historical_click:
        if i == '-':
            lists.append([])
        else:
            lists[-1].append(i)
    #print(lists)
    Historical_click.append('-')

    [str(i) for i in Historical_click]
    # cleaning de la list
    
    #Managing card

    for val in List_Card:
        print(parsing_component(str(val)))
        print('>>>>>>>>>>>>>>>')

    #We return now depending on the list 

    return Historical_click

#CallBack to Show or Hide Card 
@app.callback(
   Output(component_id='dashboard-interactivity-container-card', component_property='style'),
   Output(component_id='dashboard-interactivity-container-card', component_property='children'),
   [Input(component_id='show_card', component_property='n_clicks')])
def show_hide_card(val):

    Grid_Card = html.Div(
    [
        dbc.Row([
                            dbc.Col([List_Card[i]], width=3) for i in range(len(List_Card))
                        ], align='center'),
        Matching_Button
    ]
    )

    if val%2 == 0:
       
        return {#'backgroundColor':'blue',
                'display': 'none',
                'position': 'fixed',
                'left': '0px',
                'top': '0px',
                'margin-left': '15%',
                 'height': '70%',
                 'width': '80%',
                 'zIndex' :'7',
                'margin-top':'6.5%'
                }, Grid_Card
    if not val%2 == 0:
     
        return {#'backgroundColor':'blue',
            'display': 'block',
                'position': 'fixed',
                'left': '0px',
                'top': '0px',
                'margin-left': '15%',
                 'height': '70%',
                 'width': '80%',
                 'zIndex' :'7',
                'margin-top':'6.5%'
                }, Grid_Card

#Parsing method for component (to be removed)
def parsing_component(text):

    matches = str(re.findall(r'index.+?}',text))
    matches = re.findall('\d+', matches )
    
    return matches[0]

app.layout = html.Div([
    Title,
    Container_div
])

if __name__=='__main__':
    app.run_server(host="localhost", port=5005,debug=False,dev_tools_ui=False)

Thanks a lot :grin:

List item

Hi,

Your issue is because you are adding and removing the cards from the callback, and those cards contain buttons triggering the other callbacks. Adding components to the layout that are inputs in certain callbacks will trigger those callbacks, as explained in the documentation.

If the added components triggering the callbacks are just buttons and you don’t care about the click count, you can consider setting n_clicks=None when adding them to the layout again and preventing the update. You could also just hide the entire div by setting {"display": "none"} if you want to preserve the card states. There are certainly other approaches, but those are two simple ideas that might work for you.

Please let us know if this works!

2 Likes

First of all, thanks a lot for you’re response ! :grin:

You have totally understood what was my problem. The idea why I wanted to integrate this Grid_card in my panel that could be Show/Hide is because I wanted something that could be dynamic with time.

So the user will have the possibility to add card by himself, or by updating the databse the Grid_Card will be updated.

I found usefull to put this Grid_Card in the Show/Hide callback because this is where you could want to update the cards before showing them to the users.

I did not find out how to make the first option work; But for the second option it works perfectly. I could do a refresh button that would refresh the Grid Card and then remove this problem of triggering the button when I show/Hide the section :innocent:

So yes i will definitely go for the second idea since the first one i don’t really know where to implement it; But the second one will work with a callback to update the cars :smile:

Thanks 1000x times !

2 Likes