Create a four digit number based on buttons pressed

I’m trying to create a keypad entry interface that based on the buttons pressed it generates and displays a four digit number. The interface part is easy but rather than displaying the number based on the buttons pressed it always defaults to “1234” after pressing enter. Below is the code block. Any ideas how to get it to show, for example 2500, if I press the “2”, “5” and “0” buttons twice?

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash import html
from dash.dependencies import Input, Output, State

app = dash.Dash()

app.layout = html.Div([
html.Div([
html.Button(id=‘button-7’, value=‘7’, children=‘7’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-8’, value=‘8’, children=‘8’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-9’, value=‘9’, children=‘9’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’})
], style={‘display’: ‘flex’}),
html.Div([
html.Button(id=‘button-4’, value=‘4’, children=‘4’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-5’, value=‘5’, children=‘5’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-6’, value=‘6’, children=‘6’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’})
], style={‘display’: ‘flex’}),
html.Div([
html.Button(id=‘button-1’, value=‘1’, children=‘1’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-2’, value=‘2’, children=‘2’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-3’, value=‘3’, children=‘3’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’})
], style={‘display’: ‘flex’}),
html.Div([
html.Button(id=‘button-0’, value=‘0’, children=‘0’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-00’, value=‘00’, children=‘00’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
html.Button(id=‘button-enter’, n_clicks=0, children=‘Enter’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’})
], style={‘display’: ‘flex’}),
html.Div([
html.Output(id=‘output’, style={‘fontSize’:48, ‘width’: ‘66%’, ‘height’: ‘60px’}),
html.Button(‘Reset’, id=‘button-reset’, style={‘fontSize’:24, ‘width’: ‘33%’, ‘height’: ‘60px’}),
], style={‘display’: ‘flex’}),
])

@app.callback(
Output(‘output’, ‘children’),
[Input(‘button-1’, ‘value’),
Input(‘button-2’, ‘value’),
Input(‘button-3’, ‘value’),
Input(‘button-4’, ‘value’),
Input(‘button-5’, ‘value’),
Input(‘button-6’, ‘value’),
Input(‘button-7’, ‘value’),
Input(‘button-8’, ‘value’),
Input(‘button-9’, ‘value’),
Input(‘button-0’, ‘value’),
Input(‘button-enter’, ‘n_clicks’)])
def update_output(b1, b2, b3, b4, b5, b6, b7, b8, b9, b0, enter):
if enter > 0:
return b1 + b2 + b3 + b4
else:
return ‘’

if name == ‘main’:
app.run_server()

Dash UI

Hi, you should be using the n_clicks property of the buttons and with some form of context or store to maintain the state. Now you just concatenate the values of the first 4 button values when you push enter

1 Like

@jcuypers thanks. I’ve changed the code to utilise the n_click property but now I just get output of 1, 11, 111 etc. How do I utilise the ‘value’ property of the button?

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash import html
from dash.dependencies import Input, Output, State

app = dash.Dash()

current_number = ''

app.layout = html.Div([
    html.Div([
        html.Button(id='button-7', value='7', children='7', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-8', value='8', children='8', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-9', value='9', children='9', style={'fontSize':24, 'width': '33%', 'height': '60px'})
    ], style={'display': 'flex'}),
    html.Div([
        html.Button(id='button-4', value='4', children='4', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-5', value='5', children='5', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-6', value='6', children='6', style={'fontSize':24, 'width': '33%', 'height': '60px'})
    ], style={'display': 'flex'}),
    html.Div([
        html.Button(id='button-1', value='1', children='1', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-2', value='2', children='2', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-3', value='3', children='3', style={'fontSize':24, 'width': '33%', 'height': '60px'})
    ], style={'display': 'flex'}),
    html.Div([
        html.Button(id='button-0', value='0', children='0', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button(id='button-enter', n_clicks=0, children='Enter', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button('Reset', id='button-reset', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
    ], style={'display': 'flex'}),
    html.Div([
        html.Output(id='output', style={'fontSize':48, 'width': '100%', 'height': '60px'})
    ], style={'display': 'flex'})
])

@app.callback(
    Output('output', 'children'),
    [Input('button-1', 'n_clicks'),
     Input('button-2', 'n_clicks'),
     Input('button-3', 'n_clicks'),
     Input('button-4', 'n_clicks'),
     Input('button-5', 'n_clicks'),
     Input('button-6', 'n_clicks'),
     Input('button-7', 'n_clicks'),
     Input('button-8', 'n_clicks'),
     Input('button-9', 'n_clicks'),
     Input('button-0', 'n_clicks'),
     Input('button-enter', 'n_clicks')])

def update_output(button1_clicks, button2_clicks, button3_clicks,
                  button4_clicks, button5_clicks, button6_clicks,
                  button7_clicks, button8_clicks, button9_clicks,
                  button0_clicks, button_enter_clicks):
    global current_number
    if button1_clicks:
        current_number += '1'
        return f'{current_number}'
    elif button2_clicks:
        current_number += '2'
        return f'{current_number}'
    elif button3_clicks:
        current_number += '3'
        return f'{current_number}'
    elif button4_clicks:
        current_number += '4'
        return f'{current_number}'
    elif button5_clicks:
        current_number += '5'
        return f'{current_number}'
    elif button6_clicks:
        current_number += '6'
        return f'{current_number}'
    elif button7_clicks:
        current_number += '7'
        return f'{current_number}'
    elif button8_clicks:
        current_number += '8'
        return f'{current_number}'
    elif button9_clicks:
        current_number += '9'
        return f'{current_number}'
    elif button0_clicks:
        current_number += '0'
        return f'{current_number}'
    elif button_enter_clicks:
        number = current_number
        return f'{number}'
    else:
        return 'Enter a two-digit number using the keypad'

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

Hello @bryantjaimer,

I would highly recommend not using global variables, especially if you plan on having multiple users. You can instead use the state of the output children to get the current number listed.

Another thing, you might consider using pattern-matching callbacks, as this could significantly reduce the lines of code you have:

And then the easiest way to query the value would be parsing it from the id itself:

number = ctx.triggered_id.index #assuming pattern-matching and index is the number

How do you get “state of the output children to get the current number listed”?

1 Like

Something like this in the callback.

State(‘output’,’children’)

@jinnyzor Thanks for your help so far. So, I updated the call back and function like below which gives me and displays the value of the current button pressed:

@app.callback(
    Output('output', 'children'),
    [Input('button-1', 'n_clicks'),
     Input('button-2', 'n_clicks'),
     Input('button-3', 'n_clicks'),
     Input('button-4', 'n_clicks'),
     Input('button-5', 'n_clicks'),
     Input('button-6', 'n_clicks'),
     Input('button-7', 'n_clicks'),
     Input('button-8', 'n_clicks'),
     Input('button-9', 'n_clicks'),
     Input('button-0', 'n_clicks'),
     State('output', 'children')])
def update_output(*n_clicks):
    ctx = dash.callback_context
    button_id = ctx.triggered[0]['prop_id'].split('.')[0]
    print("Button id: ", button_id)
    button_value = button_id.split('-')[-1]
    # update the four digit number based on the button pressed
    new_number = button_value 
    return new_number

But, I want to append that button value to the previous button press values until I get a four digit number. I have tried something like this:

def display_output(*n_clicks):
    ctx = dash.callback_context
    if not ctx.triggered:
        return ''
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]
        button_value = button_id.split('-')[-1]
        output_value = ctx.inputs['output.children'] + button_value if len(ctx.inputs['output.children']) < 4 else ctx.inputs['output.children']
        return output_value

But it is coming up with:

File “”, line 53, in display_output
output_value = ctx.inputs[‘output.children’] + button_value if len(ctx.inputs[‘output.children’]) < 4 else ctx.inputs[‘output.children’]
KeyError: ‘output.children’

Any ideas?

Solved this by introducing a dcc.Store element to store the previous entered values, and then returning data from that in the call back

2 Likes

Hello @bryantjaimer,

Glad you got something that works for you, here is a way that you could accomplish the same via pattern-matching:

import dash
from dash import html, Input, Output, State, dcc, ALL, ctx

import dash_bootstrap_components as dbc

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

nums = [7, 8, 9, 4, 5, 6, 1, 2, 3, 0]

current_number = ''

app.layout = html.Div([
    html.Div([
        dbc.Col(dbc.Button(id={'index': i, 'type':'numberPad'}, children=i, style={'width':'100%', 'fontSize':24},
                           color='info'), width=4, className='pe-1 pt-1')
        for i in nums
    ], style={'display': 'flex', 'flexWrap':'wrap', 'justifyContent':'center'}),
    html.Button(id='button-enter', n_clicks=0, children='Enter', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
        html.Button('Reset', id='button-reset', style={'fontSize':24, 'width': '33%', 'height': '60px'}),
    html.Div([
        html.Output(id='output', children=[
            html.Div(id='numbers', children=''),
            html.Div(id='alert'),
        ], style={'fontSize':48, 'width': '100%', 'height': '60px'})
    ], style={'display': 'flex'})
])

@app.callback(
    Output('numbers', 'children'),
    Output('alert', 'children'),
    Input({'index':ALL, 'type':'numberPad'}, 'n_clicks'),
    Input('button-reset', 'n_clicks'),
    State('numbers', 'children'))
def update_output(_, reset, children):
    if ctx.triggered:
        if 'index' in ctx.triggered_id:
            if len(children) > 3:
                return dash.no_update, dbc.Alert('you are trying to enter more than 4 digits', color='danger',
                                                            dismissable=True)
            else:
                children += str(ctx.triggered_id.index)
                return children, dash.no_update
        if ctx.triggered_id == 'button-reset':
            return '', dash.no_update

    return dash.no_update, dash.no_update

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

This uses bootstrap, but that choice it up to you. :slight_smile:

2 Likes

Thanks for this. Works great

1 Like