Getting access to the server from my custom JS-code added to the template

Hello!

My task is to learn how to access the server from the client’s browser. More details: I want to inject my JS code into the client’s browser. I implement this code by adding my tags to the template. Questions:

  1. How can I access Bokeh objects to manipulate them?

  2. How can I call python functions on the server from my JS code in the client’s browser?

For example:

I made my context menu in the browser. But how do I contact the server to perform any operations on the server when selecting items from the context menu. For each menu item, I have to write a JS code that would start the execution of the operations I need on the server.

And can I modify objects directly on the client side in BokehJS? For example, in the click handler of a button that I created myself in the browser, call a change in the Bokeh attributes of the widgets?

There is no direct way. You can set on_change callback handlers and then update that property value from JS. Property change events are the primary mechanism to trigger callbacks on either the Python or JS sides. But a general RPC mechanism is out of scope for Bokeh and if that is what you need then you might be better served by looking at other tools.

1 Like

Thanks a lot, Bryan. I got the main idea, but some practical questions still in my mind. Let me to explain on small example below.

import random

from bokeh.models import Button, Paragraph, CustomJS
from bokeh.layouts import column
from bokeh.io import curdoc


def python_code(event):
    # some arbitrary python code:
    random_number = random.randint(0, 10)
    text_area.text = f'Python code Random number: {random_number}'
    print('Code completed successfully !!!')


create_custom_button = CustomJS(code="""
    var button = document.createElement("button");
    button.innerHTML = "My custom button";
    document.getElementsByTagName("body")[0].appendChild(button);
    
    button.addEventListener ("click", function() {
      console.log('Click on custom button')    
      alert("I need to trigger server <python_code> here...");
    });
""")

text_area = Paragraph(text='Some text')
bk_button = Button(label='Create custom button in Browser')
bk_button.js_on_event("button_click", create_custom_button)

curdoc().add_root(column(text_area, bk_button))

In this app I can create custom button (it create outside of Bokeh document scope - in HTML body directly) by pressing Bokeh button (in real project this custom button is creating by my custom JS code).

But bokeh server doesn’t know about it existence. It is possible to add onclick event handling in my JS code only. But how can I link this handling function with server?

How can I implement your idea about using on_change callback at this situation? Which object should I assign this callback to?

The Document is the foundational unit and mechanism of serialization and synchronization, so the Button has to be added somewhere to a Document in order to be synchronized between Python and JS. It’s certainly possible to add a new object to a Document (and it will be synchronized), but there would be no good way to add a new Python callback from JavaScript once it is added (as you note).

You should look for a path that lets you add everything you need from Python up-front, even it is not displayed in the page to start. Otherwise, I would definitely consider what you are describing as “fighting with the tool” and reiterate that you would probably be better served by a different library other than Bokeh.

1 Like

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 ?

You could use a DataModel that you define rather than some random widget. See this example:

bokeh/data_models.py at branch-3.0 · bokeh/bokeh · GitHub

However, I do really think that Bokeh is just not the right tool for you needs, if you are jumping through these kinds of hoops.

Cool! It’s exactly what I need. I created my JS-based context menu! And this one is linked with server!
Bryan, I’m so grateful for your help!

1 Like