I achieved my goal with the following code (using on_change callback):
A) In ninja2 template:
...
<script type="text/javascript">
var button1 = document.createElement("button");
var button2 = document.createElement("button");
var button3 = document.createElement("button");
button1.innerHTML = "Button 1";
button2.innerHTML = "Button 2";
button3.innerHTML = "Button 3";
body = document.getElementsByTagName("body")[0]
body.appendChild(button1);
body.appendChild(button2);
body.appendChild(button3);
</script>
...
B) in Bokeh app:
from bokeh.events import DocumentReady
from bokeh.models import Paragraph, CustomJS
from bokeh.io import curdoc
def button_1_python_code():
print('Run BUTTON-1 python code')
def button_2_python_code():
print('Run BUTTON-2 python code')
def button_3_python_code():
print('Run BUTTON-3 python code')
# this widget invisible and has created for link custom HTML-buttons and Bokeh server python functions
helper = Paragraph(name='', text='', visible=False)
# custom buttons onclick handlers will activate after js-callback below
activate_buttons_js = CustomJS(args=dict(helper=helper), code="""
button1.onclick = () => {helper.text = 'button_1';};
button2.onclick = () => {helper.text = 'button_2';};
button3.onclick = () => {helper.text = 'button_3';};
""")
# activation js-callback will call after whole document render
# I change 'name' attribute of helper when document rendering finished (see below)
helper.js_on_change('name', activate_buttons_js)
# this callback is called after rendering. It trigger activate_buttons_js callback
def activate_buttons(event):
helper.name = 'any new name'
# get helper text and map it to python functions
def helper_callback(attr, old, new):
""" map helper attribute values (which comes from the browser onclick handlers) and python functions """
if new == 'button_1':
button_1_python_code()
elif new == 'button_2':
button_2_python_code()
elif new == 'button_3':
button_3_python_code()
# reset helper text
helper.text = ''
# listening for changing helper text from browser
helper.on_change('text', helper_callback)
doc = curdoc()
doc.add_root(helper)
# document ready and we are ready to activate buttons
doc.on_event(DocumentReady, activate_buttons)
This work as I need, but code looks a little messy:
- I add one invisible “helper” widget to use it as link between browser and server
- I add some additional callback on DocumentReady event to run a JS callback that adds button onclick handlers.
Can I refactor this code to:
- get rid of the “helper” widget
(and / or)
- run activation JS-callback after document rendering in more elegant way ?