Confirmation dialog on button press

I’m currently using bokeh serve and wanted to have a confirmation dialog when a button is pressed before executing some python code. There are some old posts from 2016 about dialogs, but that seems to be deprecated. I’ve come up with the code below. Basically I use two hidden PreText widgets to trigger javascript and python calls when their text is changed. It seems to work, but I feel like there is a better way. Thoughts?

import uuid                                                                     
                                                                                
from bokeh.io import curdoc                                                     
from bokeh.layouts import column                                                
from bokeh.models import Button, CustomJS, PreText                              
                                                                                
# Global to store everything                                                    
STATE = {}

# What function to call if confirmation is yes                                  
STATE['confirm_func'] = None                                                    
                                                                                
# triggers confirmation by changing the value in confirm_text                   
def trigger_confirm(text):                                                      
    # add in uuid so it's allways different                                     
    STATE['confirm_text'].text = str(uuid.uuid4()) + text                       
                                                                                
# handles confirmation when value in confirm_res is changed                     
def handle_confirm(attr, old, new):                                             
    if STATE['confirm_func'] is None:                                           
        return                                                                  
    if STATE['confirm_res'].text.strip() == 'yes':                              
        STATE['confirm_func']()                                                 
    STATE['confirm_func'] = None                                                
                                                                                
# Call right after button is pressed                                            
def button_callback(event):                                                     
    # Set function to call on yes result                                        
    STATE['confirm_func'] = button_action                                       
                                                                                
    trigger_confirm("Are you sure you want to push the button?") 
    # This occurs right away, doesn't wait for dialog to be done               
                                                                                
# Actual action to perform after confirmation dialog                            
def button_action():                                                            
    print("=====Button action done=====")                                       
                                                                                
STATE['confirm_text'] = PreText(text="")                                        
STATE['confirm_res'] = PreText(text="")                                         
                                                                                
confirm_js = CustomJS(                                                          
    args=dict(confirm_text=STATE['confirm_text'], confirm_res=STATE['confirm_res']),
    code=                                                                       
    '''                                                                         
        // Remove uuid from text                                                
        let confirm_str = confirm_text.text.substring(36);                      
                                                                                
        // Create dialog                                                        
        let dialog_ans = confirm(confirm_str);                                  
        let res = '';                                                           
        if (dialog_ans) {                                                       
            console.log("yes");                                                 
                                                                                
            // handle where no change in result still triggers callback         
            if (confirm_res.text == 'yes') {                                    
                res = 'yes ';                                                   
            } else {                                                            
                res = 'yes';                                                    
            }                                                                   
        } else {                                                                
            console.log("no");                                                  
                                                                                
            // handle where no change in result still triggers callback         
            if (confirm_res.text == 'yes') {                                    
                res = 'no ';                                                    
            } else {                                                            
                res = 'no';                                                     
            }                                                                   
        }                                                                       
        console.log(res);                                                       
        confirm_res.text = res;                                                 
    '''                                                                         
)                                                                               
                                                                                
# Set up callbacks                                                              
STATE['confirm_text'].js_on_change('text', confirm_js)                          
STATE['confirm_res'].on_change('text', handle_confirm)                          
                                                                                
# Generic button                                                                
STATE['button'] = Button(label='Do something')                                  
STATE['button'].on_click(button_callback)                                       
                                                                                
# Hide the dummy text widgets                                                   
STATE['confirm_res'].visible = False                                            
STATE['confirm_text'].visible = False                                           
                                                                                
                                                                                
# Add everything to doc so it syncs                                             
curdoc().add_root(column(STATE['confirm_text'], STATE['confirm_res'], STATE['button']))

Bokeh 3.0 added a modal Dialog model: dialogs — Bokeh 3.0.3 Documentation

You can add Bokeh Button objects to it with their own callbacks that do whatever is appropriate (e.g. including hiding the dialog afterwards).

Much appreciated. I did not know that. Unfortunately, for other reasons, I’m stuck on Python 3.6 at the moment so I cannot upgrade. I’ll just live with my hack until I can get off 3.6 and upgrade Bokeh versions.

Hopefully soon! Python 3.6 reached End-of-Life and is unsupported for over a year now.