How can I trigger a JavaScript callback from a Python callback?

I apologize for posting a duplicate question, I know a similar question was asked at Call Bokeh Server App Python Function from Javascript but I do not understand it. Could you please help me understand why js_on_change is not triggered when I change the value of dmmy through a Python callback and what do I need to do to trigger it?

from bokeh.models.callbacks import CustomJS
import pandas as pd
from bokeh.plotting import ColumnDataSource
from bokeh.models.widgets import Button, DataTable, TableColumn
from bokeh.models import TextInput
from bokeh.layouts import row, column
from bokeh.io import curdoc

js_dwnld = """
console.log("got here!")
var top_n = dwnld_n.value;
var csv = src.data;
var filetext = 'a,b,c\\n';
var i;
for (i=0; i<top_n; i++) {    
    var currRow = [csv['a'][i].toString(),
                   csv['b'][i].toString(),
                   csv['c'][i].toString().concat('\\n')]
    var joined = currRow.join();
    filetext = filetext.concat(joined);    
}
var filename = 'results.csv';
var blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
    navigator.msSaveBlob(blob, filename);
} 
else {
    var link = document.createElement("a");
    if (link.download !== undefined) { // feature detection
        // Browsers that support HTML5 download attribute
        var url = URL.createObjectURL(blob);
        link.setAttribute("href", url);
        link.setAttribute("download", filename);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }
}
"""

def dummy_dwnld():
    dmmy.value = 'abc'

data = pd.DataFrame({'a':[1,2,3], 'b':[2,3,4], 'c':['d','e','f']})
tab_data = ColumnDataSource(data)
tab_cols = [TableColumn(field="a", title="a"), TableColumn(field="b", title="b"), TableColumn(field="c", title="c")]
tab = DataTable(source=tab_data, columns=tab_cols)

dwnld_top = TextInput(value=str(len(data)), title="Number of rows to download")
dwnld = Button(label='Download')
dwnld.on_click(dummy_dwnld)

dmmy = TextInput(value='')
dmmy.js_on_change('value', CustomJS(args=dict(src=tab_data, dwnld_n=dwnld_top), code=js_dwnld))

layout = row(column(dwnld_top, dwnld), tab)

curdoc().add_root(layout)

I think the reason is that dmmy is not in the document. Try adding it to the document, even as a dummy value in args for some CustomJS that’s attached to some model that is in the document should work.

Also I think you could also just use

dwnld.js_on_click(CustomJS(...))

without needing to make any “dummy” property changes at all.

Thanks @p-himik, I made dmmy a Div with colour set to white instead of a TextInput and added it to the column in layout. On setting js_on_change on its text attribute and changing it from dummy_dwnld it works well.

@Bryan, yes I could but what I have are three dictionaries made up of hundreds of widgets that are used by the user to filter and aggregate the DataTable. I would like to check which of these widgets have been changed from their default values by the user and pass these as text to js_dwnld for them to be put into the CSV.