How to get the column index of a selected cell in a scroll-DataTable?

Hi,
I want to get the column index or name of a selected cell in a DataTable.
The table has over hundred rows and about fifty columns so it is scrollable.

Here is the way I populate the DataTable:

table_source = ColumnDataSource()
table_columns = []
data_table = DataTable(source=table_source, columns=table_columns, width=900, height=900, autosize_mode="none")# autosize_mode="fit_columns" takes time

def dat_table():
    new_table_source = dict(ColumnDataSource(df1).data)
    table_columns = []
    for col in df1.columns:
        if col in DateCols:
            table_columns.append(TableColumn(field=col, title=col, width=100, formatter=DateFormatter(format="%d.%m.%Y %H:%M")))
        else:
            table_columns.append(TableColumn(field=col, title=col, width=np.ceil(6.5*len(col)).astype('int')))
    data_table.source.data = new_table_source
    data_table.columns = table_columns
    
# Update table:
dat_table()

I am most often displaying the table in JupyterLab, but I would like the code to also work in a bokeh served app started with bokeh serve --show my_app.py displayed in a browser.

Based on this thread:

I tried following code:

def table_selection(attr, old, new):
    print("old:", old)
    print("new:", new)
    print("indices", data_table.source.selected.indices)
    print("line_indices", data_table.source.selected.line_indices)
    print("multiline_indices", data_table.source.selected.multiline_indices)

data_table.source.selected.on_change('indices', callback)

But none of the attributes of ColumnDataSource.selected provides the column index. Only the row(s) of the selected cell(s) will be returned.

I also tried a CustomJS approach based on this thread:

row_print = TextInput(value = "", title = "Row:", width = 200)
column_print = TextInput(value = "", title = "Column:", width = 200)

source_code = """
let grid = document.getElementsByClassName('grid-canvas')[0].children; // This line works without ".children" with Python 3.11 bokeh 3.1.0, but not with ".children" 
let row, column = '';

for (let i = 0,max = grid.length; i < max; i++){
    if (grid[i].outerHTML.includes('active')){
        row = i;
        for (let j = 0, jmax = grid[i].children.length; j < jmax; j++)
            if(grid[i].children[j].outerHTML.includes('active')) 
                { column = j }
    }
}

row_print.value = String(row);
column_print.value = String(column); """

callback = CustomJS(args = dict(row_print=row_print, column_print=column_print), code = source_code)
data_table.source.selected.js_on_change('indices', callback)

And also on that thread:

row_print = TextInput(value = "", title = "Row:", width = 200)
column_print = TextInput(value = "", title = "Column:", width = 200)

source_code = """
let grid = document.getElementsByClassName('grid-canvas')[0];
let active_row = grid.querySelectorAll('.active')[0];  // This line doesn't works with Python 3.11 bokeh 3.1.0

if (active_row!=undefined){
    var active_row_ID = Number(active_row.children[0].innerText);
    for (var i=1, imax=active_row.children.length; i<imax; i++){
        if (active_row.children[i].className.includes('active')){
            var active_col_ID = i-1;
        }
    }

    row_print.value = String(active_row_ID);
    column_print.value = String(active_col_ID); 
    
    var active_cells = grid.querySelectorAll('.active');
    for (i=0, imax=active_cells.length;i<imax;i++){
        active_cells[i].classList.remove('active');
    }

    cb_obj.indices = [];
}

"""

callback = CustomJS(args = dict(row_print=row_print, column_print=column_print), code = source_code)
data_table.source.selected.js_on_change('indices', callback)

Though, I encounter following problems with these CustomJS approaches:
With Python 3.9 and bokeh 2.4.2, I can run these two scripts, but the column (and row) index get messed up as soon as I use the scroll bars of the table.

With Python 3.11. and bokeh 3.1.0, I didn’t manage to run the JS code (see comments in the code above).

Are there any other ways I can obtain the column number of a selected cell?

1 Like

This information is not currently exposed. It’s possible (probable?) that the underlying third-party SlickGrid library makes this available somehow, but someone would need to propose/implement new features to make them available at the Bokeh level some way.

Edit: there might be some solution involving scanning the DOM but I would expect such approaches to be very fragile or unreliable. In any case I’ve never tried so I can’t offer any concrete advice in that direction.

I have been looking for a better way to do this too, and am very interested in advancements on this.
The JS also doesn’t work for me when embedding the table like this:

<div class="table-container">{{ embed(roots.data_table) | indent(10) }}</div>

Unless I change the selected element to this

document.getElementsByClassName('table-container")[0].children[0].children[0].shadowRoot.querySelector("div").getElementsByClassName("grid-canvas grid-canvas-top grid-canvas-left")[0].children

Which is far from ideal!

The way I came up with the customJS in the stackoverflow is to inspect the page elements of the bokeh server in the browser console. Since the html structure of bokeh models changed in 3.1.0 these kind of callbacks need to be updated to match the new layout.

Thanks all for your feedback.

@Bryan

someone would need to propose/implement new features to make them available at the Bokeh level some way.

I opened following feature request:

2 Likes

By the way, you can use JS to get the column and the normal selected.indices to get the row, even if you scroll vertically (Not horizontally though, probably)
Also, my JS code above does work in bokeh 3.1, but might be specific to my layout
Best way to check is to go to the JS console and finding a formula to reach the grid-canvas

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