Use a variable as argument to CustomJS

CustomJS accepts arguments that are passed to javascript during callback.

Though, I observed that if I use a variable as argument to a CustomJS callback, then modify the value of the variable before triggering the callback, the callback uses an outdated value of the variable.
This is different from python callbacks, in which the current value of inputs variables are used.

Here is a short example comparing the behavior of Python and CustomJS callbacks, in which I also introduced what I see as a workaround for CustomJS to use an up to date argument, using a paragraph object to store the input (launch with bokeh serve --show):

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

js_code = """
alert("Input is: " + input +
", Input from paragraph is: " + paragraph.text)
"""

button_py = Button(label="Start python-callback")
button_js = Button(label="Start js-callback")

input = 1
paragraph = Paragraph(text=str(input))

def py_callback():
    print(f"Input is {input}")
    print(f"Input from paragraph is {paragraph.text}")

# Callbacks:
button_py.on_click(py_callback)
button_js.js_on_click(CustomJS(args=dict(input=input, paragraph=paragraph), code=js_code))

input = 2
paragraph.text = str(input)

curdoc().add_root(column(button_py, button_js))

The Python callback leads as expected to the print log:
“Input is 2
Input from paragraph is 2”
The CustomJS callback leads to “Input is: 1, Input from paragraph is: 2”, I had expected “Input is: 2, Input from paragraph is: 2”.

Is there another more straightforward way to dynamically update the arguments of CustomJS callbacks than passing them to a bokeh model?

My system:
Windows 10
Python 3.12.0
bokeh 3.3.0

This is standard Python behavior, where variables are just names for values, and not references to memory:

>>> a = 10
>>> b = a
>>> b
10
>>> a = 20
>>> b # still 10
10

For what you are looking for, your best is probably to create a DataModel subclass. A DataModel is a very lightweight user-defined model, created with only a small amount of declarative Python, that can be synchronized across the Python ↔ JS boundary like any standard Bokeh object:

class Params(DataModel):
    foo = Float(default=0.1)
    bar = Int(default=10)

If you pass an instance of a Params to a CustomJS it will not be “evaluated” until right before it is serialized to generate the output. There is a complete example here:

https://github.com/bokeh/bokeh/blob/branch-3.4/examples/plotting/customjs_expr.py

Based on the example you referred to, the implementation of DataModel to my example would be as follows:

from bokeh.core.properties import String, expr
from bokeh.model import  DataModel
from bokeh.io import curdoc
from bokeh.models import Button, CustomJS
from bokeh.layouts import column

js_code = """
alert("Input from DataModel is: " + Data_Model.text)
"""

button_py = Button(label="Start python-callback")
button_js = Button(label="Start js-callback")

input = 1
class string_buffer(DataModel):
    text = String()

file_content = string_buffer(text=str(input))

def py_callback():
    print(f"Input is from DataModel is {file_content.text}")

# Callbacks:
button_py.on_click(py_callback)
button_js.js_on_click(CustomJS(args=dict(Data_Model=file_content), code=js_code))

input = 2
file_content.text = str(input)

curdoc().add_root(column(button_py, button_js))
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.