Can't catch an update of a CDS via CustomJS callback

I’m using a DataTable to interactively update a secondary CDS which I will be using in another part of my project.
The problem is that although I’m able to update the CDS, I’m not able to catch that it has been changed.
In the example below I’m using the ‘reliability’ of a car brand, which you can set interactively, to determine whether a rental company who uses that car brand to be in business or not.

In the callback function I’m trying specifically to update the whole ‘data’ feature of the CDS, and also send a change.emit(), to provoke the second callback (‘callback2’). But I’m not capable of calling ‘callback2’:

from bokeh.models import ColumnDataSource, DataTable, TableColumn, CheckboxEditor, CustomJS
from bokeh.io import output_file
from bokeh.plotting import show

# Main table
car_source = ColumnDataSource(dict(
    car_brand=['Honda', 'Toyota'],
    reliable=[True, True]
))

table_columns = [
    TableColumn(field='car_brand', title='Car manufacturer'),
    TableColumn(field='reliable', title='Reliable?', editor=CheckboxEditor())
]
main_table = DataTable(
    source=car_source,
    columns=table_columns,
    editable=True,
    width= 200
)

# Secondary CDS
rental_source = ColumnDataSource(dict(
    car_rental=['Sixt', 'Seventh', 'Eighth'],
    car_brand=['Honda', 'Honda', 'Toyota'],
    in_business=[True, True, True]
))

# callback functions
callback1 = CustomJS(
    args=dict(car_source=car_source, rental_source=rental_source),
    code = """
        const car_data = car_source.data;
        var rental_data = rental_source.data;
        for (let i = 0; i < car_data['car_brand'].length; i++) {
            let this_car_brand = car_data['car_brand'][i];
            for (let j = 0; j < rental_data['car_rental'].length; j++) {
                if (rental_data['car_brand'][j] === this_car_brand) {
                    rental_data['in_business'][j] = car_data['reliable'][i];
                    console.log('Car rental ' + rental_data['car_rental'][j] + ' in business? ' + rental_data['in_business'][j]);
                }
            } 
        }
        rental_source.data = rental_data;
        rental_source.change.emit();
    """
)

callback2 = CustomJS(code="""
    console.log('Car rentals has been updated');
""")

car_source.js_on_change('patching', callback1)
rental_source.js_on_change('data', callback2)

show(main_table)

Not sure if this is optimal/will fit in your real use case, but you can trigger CustomJS within other CustomJS by calling .execute()

So you can define callback2 before callback1, then pass callback2 as an arg to callback1, and call execute() on it at the end to explicitly trigger it:

# callback functions

callback2 = CustomJS(code="""
    console.log('Car rentals has been updated');
""")

callback1 = CustomJS(
    args=dict(car_source=car_source, rental_source=rental_source
              ,cb2=callback2),
    code = """
        const car_data = car_source.data;
        var rental_data = rental_source.data;
        for (let i = 0; i < car_data['car_brand'].length; i++) {
            let this_car_brand = car_data['car_brand'][i];
            for (let j = 0; j < rental_data['car_rental'].length; j++) {
                if (rental_data['car_brand'][j] === this_car_brand) {
                    rental_data['in_business'][j] = car_data['reliable'][i];
                    console.log('Car rental ' + rental_data['car_rental'][j] + ' in business? ' + rental_data['in_business'][j]);
                }
            } 
        }
        console.log('here')
        rental_source.data = rental_data;
        //rental_source.change.emit() // don't need anymore
        cb2.execute()
        ;
    """
)




car_source.js_on_change('patching', callback1)

Thank you @gmerritt123
That worked as a charm:-)

To make it work in my real use case I will need to pass some arguments to cb2. I will figure that out (I hope)
Many thanks.

1 Like