Link custom javascript

I am trying to achieve to achieve a python callback from a javascript event. Basically I have a table and if clicked on a cell, I wall to call a python function which will modify the data of this cell.

I read accross the links documentation and this issue.

The only way I see is to update (a hidden) input element with some id and react on that change. Is that correct or do I miss a better way, to bind a click event on a random html-element to a python function?

I tried to modify the mentioned issues solution to my use-case:

import panel as pn
from bokeh.sampledata.autompg import autompg

js = {
    '$': 'https://code.jquery.com/jquery-3.4.1.slim.min.js',
}

pn.extension(js_files=js)

html = autompg.head(10).to_html(classes=['example', 'panel-df'])

btn = pn.widgets.Toggle(name='custom click event', value=False)
# comm to return informations from javascript to python
comm_js_py = pn.widgets.StaticText(name='selected_ids', value='')#style={'visibility': 'hidden', 'width': 0, 'height': 0, 'overflow': 'hidden'}, margin=0)

btn.jscallback(args={'tab':html, "comm_js_py":comm_js_py}, value="""

$(".example td").click(function(event) {
    console.log('clicked', this.textContent, comm_js_py.text)
    comm_js_py.setv({text: this.textContent}, {silent: true})
    comm_js_py.properties.text.change.emit()
    
})
""")

# ouput div to write some information retrieve on python side
output_div = pn.widgets.StaticText(width=500, value='abc')
def update_output(*events):
    print(">",events)
    output_div.value += events[0].new + '<br>'
comm_js_py.param.watch(update_output, ['value'], onlychanged=False)

pn.Column(comm_js_py, btn, pn.Row(html, output_div), sizing_mode="stretch_width").show()

How ever, the StaticText widget does not get updated, even that the console.log statements look good. What am I doing wrong?

Also I would like to initialize the event listener without the button click, but I guess I need to pass the comm_js_py object, and I do not know how to to this without the click btn.jscallback.

There is currently no recommended way of doing this (although it’s probably possible using a hacky approach). We are working on allowing JS callbacks to update a so called DataModel, which will make this possible without relying on weird hacks.

That said could you use the DataFrame widget or the Tabulator widget which will be available in the next release? Both provide a parameter which reflects the current selection.

Sorry for the first post, i read bad.

For trigger a Python callback I use a bokeh Slider, because you can have that object in javascript identified by its name with Bokeh.documents[0].get_model_by_name(“dummy_slider”).
There is a Issue in panel to expose the names, but I think it is not solved.
Note that I delete the button, if you have a button and a custom callback you can use the args as in your example and it is more easy the solution, but not so practical. With the html element you can send a “”"<script></script>""" where you can define all the JS functions you want, transparent for the user.

With the slider I look the index of the table clicked, and set the value of whatever I want with the help of the slider value. It is a hacky solution, but it works perfectly for me.

import panel as pn
from bokeh.sampledata.autompg import autompg
from bokeh.models import  Slider

slider = Slider(name="dummy_slider", visible=False, value=0, start=0, end=100000000)

js = {'$': 'https://code.jquery.com/jquery-3.4.1.slim.min.js',}
pn.extension(js_files=js)

html = autompg.head(10).to_html(classes=['example', 'panel-df'])

comm_js_py = pn.widgets.StaticText(name='selected_ids',value='')#style={'visibility': 'hidden', 'width': 0, 'height': 0, 'overflow': 'hidden'}, margin=0)

JS_code = """<script> 
slider = Bokeh.documents[0].get_model_by_name("dummy_slider");
    
$(".example td").click(function(event) {
    slider.value = parseFloat(this.parentElement.firstElementChild.textContent);
    console.log('clicked', this.textContent, this.parentElement);
    trigger_element = this.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.firstElementChild
    trigger_element.textContent = this.textContent;
    })
</script>
"""

html += JS_code


# ouput div to write some information retrieve on python side
output_div = pn.widgets.StaticText(width=500, value='abc')

# @pn.depends(comm_js_py, watch=True)
def update_output(attr, old, new):
    print (attr, old, new, slider.value)
    output_div.value += str(old) + str(autompg.loc[slider.value]) + '<br>'

slider.on_change("value", update_output)

pn.Column( comm_js_py, slider, pn.Row(html, output_div),  sizing_mode="stretch_width").show()

Thank you very much, that helped me a lot. The problem seems to be, that changes on Text elements seem to not get triggered properly. I tried to change the Slider object to an TextInput and this way it stopped reacting on the changes. I am not sure whether this is a bug or if there is some other reason the on_change event does not work on the TextInput Element?

Having a way to update this DataModel from JS really would be great. Tried the DataFrame widget in the past, but quite some different trouble with it, since my table is actually way more complex then the example I provided and the DataFrame widget felt not like a good fit.

you can try a Div from bokeh.models

import panel as pn
from bokeh.sampledata.autompg import autompg
from bokeh.models import  Div

div = Div(name="dummy_div", visible=False)


js = {'$': 'https://code.jquery.com/jquery-3.4.1.slim.min.js',}
pn.extension(js_files=js)

html = autompg.head(10).to_html(classes=['example', 'panel-df'])

comm_js_py = pn.widgets.StaticText(name='selected_ids',value='')#style={'visibility': 'hidden', 'width': 0, 'height': 0, 'overflow': 'hidden'}, margin=0)

JS_code = """<script> 
div = Bokeh.documents[0].get_model_by_name("dummy_div");
    
$(".example td").click(function(event) {
    console.log('clicked', this.textContent);
    trigger_element = this.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.firstElementChild
    trigger_element.textContent = this.textContent;
    div.text = this.textContent;
    })
    
</script>
"""

html += JS_code

# ouput div to write some information retrieve on python side
output_div = pn.widgets.StaticText(width=500, value='abc')

# @pn.depends(comm_js_py, watch=True)
def update_output(attr, old, new):
    print (attr, old, new, slider.value)
    output_div.value += str(old) + div.text + '<br>'

div.on_change("text", update_output)

pn.Column( comm_js_py, slider, div, pn.Row(html, output_div),  sizing_mode="stretch_width").show()