Dynamically create callbacks on buttons inside existing callback

Hello the community,

I am trying to build an e-commerce website using dash that can be found https://carrefourcontactfontenilles.herokuapp.com/

The app contains 3 tabs :

  • “Catalogue de produits” which is the products catalogue
  • “Panier” which will be the basket of the customer
  • “Validation commande” which will be to validate the order

Actually I am done with the fist tab “Catalogue de produits”
If you click on it, and use the drop downs present in the navbar you will see the correspond products.

My issue now is that I want to assign a callback for every button “Ajouter” (add) present on each product card, which will update as output the basket list of the customer.

After have read a lot on this forum, I have found the below method to assign a callback having input all the buttons (each button id is the article# present in the column data[‘Article’ ] :

List_button = data['Article'].tolist()
input_list=[Input('{}'.format(tool),'n_clicks_timestamp') for tool in List_button]
		
@app.callback(Output('session_3','children'),
				  input_list)

def update_panier2(b_list=[('b{}'.format(tool)) for tool in range(1,len(List_button))]):
    try:
        INDEX=b_list.index(max(filter(lambda x: x is not None,b_list)))
        return html.Div([List_button[INDEX]])

I use an output in a html.div for test purpose to return the reference# of the corresponding product where the button has been clicked, with ‘data’ being my database with more than 6,000 products. I used ‘n_clicks_timestamp’ to identify the latest button clicked.

However doing 6,000 callback in one time make the app impossible to use.

I am trying now to create the button callbacks inside a function which is inside the callback which refresh the product catalogue based on the drop down selection:

#update product list
@app.callback(
    [Output('product_list', 'children')
	],
    [Input('cat1', 'value')])


def update_layout(department):
    
    fi1=data[data['Cat2'] == str(department)]
    list_nom=fi1['Name'].tolist()
    list_EAN=fi1['EAN'].tolist()
    list_pack=fi1['Pack'].tolist()
    list_prix=fi1['Prix'].tolist()
    list_ref=fi1['Article'].tolist()
    #input_list=[Input('{}'.format(tool),'n_clicks_timestamp') for tool in list_ref]
    
    
    rws= len(fi1) 
    title="test"
    gridLayout = []
    card=[]
    
    for r in range(0, rws):
             
        try:
			   
            title=list_nom[r]
            EAN=list_EAN[r]
            Pack = list_pack[r]
            Prix = list_prix[r]
            Ref = list_ref[r]
				
                
            image_filename = 'Picture/' + str(EAN) + '.png'
            encoded_image = base64.b64encode(open(image_filename, 'rb').read())
            
            card2=dbc.Col([
            dbc.Card([		
            dbc.CardImg(src='data:image/png;base64,{}'.format(encoded_image.decode()), top=True),
            dbc.CardBody([
            html.H5(title, className="card-title"),
            html.P(Pack,className="card-text",),
            ],
            style={"height": "10rem"}),		
						dbc.Col([dcc.Dropdown(id='demo-dropdown',style={'font-size': "130%"},value=1,options=[{'label': '1', 'value': 1},
														                  {'label': '2', 'value': 2},
																		  {'label': '3', 'value': 3},
																		  {'label': '4', 'value': 4},
																		  {'label': '5', 'value': 5},
																		  {'label': '6', 'value': 6},
																		  {'label': '7', 'value': 7},
																		  {'label': '8', 'value': 8},
																		  {'label': '9', 'value': 9},
																		  {'label': '10', 'value': 10},
														                   ],placeholder="Quantité")]),
						dbc.Col([dbc.Button('Ajouter',id=str(Ref),color="primary",n_clicks=0)]),
			    dbc.CardFooter(html.H4('Reference : ' + str(Ref))),
				dbc.CardFooter(html.H4(str(Prix) + ' EUR'))
				])],md=3,xs=6,sm=6,lg=3,xl=3)
                
            if r!=rws+1:
                card.append(card2)
            elif r == 1:
                card.append(card2)
            else:
                card.append(",",card2)
        
        except:

            title=list_nom[r]
            EAN=list_EAN[r]
            Pack = list_pack[r]
            Prix = list_prix[r]
            Ref = list_ref[r]
				
                
            image_filename = 'Picture/non_dispo2.png'
            encoded_image = base64.b64encode(open(image_filename, 'rb').read())


            card2=dbc.Col([
            dbc.Card([		
            dbc.CardImg(src='data:image/png;base64,{}'.format(encoded_image.decode()), top=True),
            dbc.CardBody([
            html.H5(title, className="card-title"),
            html.P(Pack,className="card-text",),
            ],
            style={"height": "10rem"}),		
						dbc.Col([dcc.Dropdown(id='demo-dropdown',value=1,style={'font-size': "130%"},options=[{'label': '1', 'value': 1},
														                  {'label': '2', 'value': 2},
																		  {'label': '3', 'value': 3},
																		  {'label': '4', 'value': 4},
																		  {'label': '5', 'value': 5},
																		  {'label': '6', 'value': 6},
																		  {'label': '7', 'value': 7},
																		  {'label': '8', 'value': 8},
																		  {'label': '9', 'value': 9},
																		  {'label': '10', 'value': 10},
														                   ],placeholder="Quantité")]),
						dbc.Col([dbc.Button('Ajouter',id=Ref,color="primary")]),
			    dbc.CardFooter(html.H4('Reference : ' + str(Ref))),
				dbc.CardFooter(html.H4(str(Prix) + ' EUR'))
				])],md=3,xs=6,sm=6,lg=3,xl=3)
                
            if r!=rws+1:
                card.append(card2)
            elif r == 1:
                card.append(card2)
            else:
                card.append(",",card2)
			
			

    fi2=data[data['Cat2'] == str(department)]
    List_button = fi2['Article'].tolist()
    input_list=[Input('{}'.format(tool),'n_clicks_timestamp') for tool in List_button]
		
    @app.callback(Output('session_3','children'),
				  input_list)
		
    def update_panier2(b_list=[('b{}'.format(tool)) for tool in range(1,len(List_button))]):
        try:
           INDEX=b_list.index(max(filter(lambda x: x is not None,b_list)))
           return html.Div([List_button[INDEX]])
        except:
           return html.Div('not working-:(')	
    
	
	
    c=html.Div(card,className='row') 
    gridLayout.append(c)
    
    
    return gridLayout

I have an error on the App
dash.exceptions.DuplicateCallbackOutput:
You have already assigned a callback to the output
with ID “session_3” and property “children”. An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.

It is my first time using dash and I am blocked. If you have some suggestion on the way I can achieve my objective I thank you in advance

Thank you
David

Check out the new pattern-matching callbacks feature released on Friday in Dash 1.11.0. It was built exactly for this use case.

:slight_smile: thanks a lot chriddyp I have tested it and it works !!

Hello @chriddyp :slight_smile: ,

I was wondering if you could please help me one more time.
I used the pattern-matching callbacks features for my buttons with state being a dropdown value.

I am using now the JSON dash.callback_context.states_list converted to DataFrame where I filter only the button id with state value > 0 (button and corresponding dropdown having the same ID)

I have tried the below code on my Notebook, which work :

# example of JSON dash.callback_context.states_list, from the callback output
json_file=[
  [
    {
      "id": {
        "index": "2148467",
        "type": "drop"
      },
      "property": "value",
      "value": 4
    },
    {
      "id": {
        "index": "1090464",
        "type": "drop"
      },
      "property": "value",
      "value": 0
    }
  ]
]

# convert json file into dataframe
fi1 = pd.DataFrame.from_dict(json_file[0], orient='columns')

# get number of clicks>0
fi1=fi1[fi1['value']>0]

#Retrieve button id 
fi1['Reference'] = fi1['id'].apply(lambda x: x.get('index'))
fi1=fi1[['Reference','value']]
fi1

On my notebook I get the result needed :
plot

However when I paste the same code in my dash app :

@app.callback(

    Output('session_1', 'children'),

    [Input({'type': 'add_button', 'index': ALL}, 'n_clicks')],

    [State({'type': 'drop', 'index': ALL}, 'value')],

    )

def display_output(n,value):

    ctx = dash.callback_context

    ctx_msg = json.dumps(ctx.states_list,

     indent=2)

    fi1 = pd.DataFrame.from_dict(ctx_msg[0], orient='columns')

    fi1=fi1[fi1['value']>0]

    fi1['Reference'] = fi1['id'].apply(lambda x: x.get('index'))

    fi1=fi1[['Reference','value']]

    final=fi1.to_json(orient='index')

    

    return (html.Pre(final))

I received the below error_message :

error

Could you please give me a clue where I am wrong

Thank you

David

Ok I have replaced

ctx_msg = json.dumps(ctx.states_list,indent=2)

by

ctx_msg = ctx.states_list

it works now