Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataTable with AjaxDataSource won't progmatically select row #9221

Open
sistemicorp opened this issue Sep 11, 2019 · 1 comment
Open

DataTable with AjaxDataSource won't progmatically select row #9221

sistemicorp opened this issue Sep 11, 2019 · 1 comment

Comments

@sistemicorp
Copy link

sistemicorp commented Sep 11, 2019

Expected Behaviour: Programitcally selecting the row in a DataTable should scroll the row into view. This works with a ColumnDataSource, but does not work with an AjaxDataSource. Previous discussion of this issue can be found here. The specific behaviour that I ultimately want, is that when table data is updated, I want the last new data to be in the Users view, which means scrolling to the last row in the table.

Observed Behaviour: The row is not selected, the table does not scroll to row.

bokeh version: 1.3.4
browser Chrome: Version 76.0.3809.132 (Official Build) (64-bit)
host OS: Ubuntu 18.04
python: Python 3.6.8 (default, Aug 20 2019, 17:12:48) [GCC 8.3.0] on linux

Minimal Code:
Because the issue is only seen with an AjaxDataSource, there are two pieces of code, one for the Flask server providing the data, and the other being the bokeh code. Start the flask server in one terminal, and the bokeh code in another. The minimal code supports testing both "append/replace" modes of the AjaxDataSource, as commented in the source.
Flask server:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
from flask import Flask, jsonify
from flask_cors import CORS,cross_origin

# install cors: pip3 install -U flask-cors

app = Flask(__name__)
CORS(app, support_credentials=True)
PAGE_URL = "/test"
MODE = ["append", "replace"][1]  # change index 0/1 to get different modes

G = {  # globals...
    "table": {"a": []},
}

@app.route(PAGE_URL + "/table", methods=['POST', 'OPIONS'])
@cross_origin(supports_credentials=True)
def url__table_data():
    # Ajax URL, add a timestamp to the data stream, either whole table (replace) or last item (append)
    G["table"]["a"].append("{}".format(time.time()))
    if MODE == "append": return jsonify({"a": [G["table"]["a"][-1]]})
    else: return jsonify(G["table"])

# Ajax URL: http://127.0.0.1:6800/test/table
app.run(host="0.0.0.0", port=6800, debug=False)

The bopkeh code uses two different methods to try and select the last row of the DataTable. One is a callback when the source data changes, and the other is thru a button that the user can press. Neither method causes the last row to be selected.
The bokeh code:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from bokeh.layouts import layout, column
from bokeh.models.widgets import Div, DataTable, TableColumn
from bokeh.models import AjaxDataSource, CustomJS
from bokeh.models.widgets.buttons import Button
from bokeh.io import show

MODE = ["append", "replace"][1]  # change index 0/1 to get different modes

source = AjaxDataSource(data=dict(a=[]), data_url="http://127.0.0.1:6800/test/table",
                        polling_interval=1000, mode=MODE)

# The GOAL of this callback is to scroll the DataTable to the last row,
# every time the datatable gets new data...
callback = CustomJS(args=dict(source=source),
                    code="""
                        var last_row_idx = source.get_length();
                        if (last_row_idx) source.selected.indices = [last_row_idx - 1];
                        //console.log("ads", cb_obj, source, (last_row_idx - 1));
                    """)
source.js_on_change("data", callback)

columns = [TableColumn(field=c) for c in source.data.keys()]
dt_table = DataTable(source=source, columns=columns, selectable=True, scroll_to_selection=True)

button = Button()
button.js_on_click(CustomJS(args=dict(source=source), code="""
    var last_row_idx = source.get_length();
    if (last_row_idx) source.selected.indices = [last_row_idx - 1];
    //console.log("Button", cb_obj, source, (last_row_idx - 1));
"""))

doc_layout = layout()
doc_layout.children.append(column(Div(text="""<h1>Hello World!</h1><p>Using an AjaxDataSource.</p>""")))
doc_layout.children.append(column(button, dt_table))

show(doc_layout)

Note that the issue does not occur with a ColumnDataSource, only with an AjaxDataSource.

I have used the console output in the above example to inspect the source.selected.indices and indeed this value is updated by the callbacks above (both button and the data change callback). But it seems that this does not trigger the table to scroll. Also note that I triedd adding a source.selected.change.emit(); to manually trigger the table to scroll, but that also did nothing.

The workaround, as discussed in the above linked discussion, is to call SlickGrid directly, but I have yet to get to trying that out... (TODO).

@bryevdv
Copy link
Member

bryevdv commented Sep 20, 2019

Thanks for the complete report, @sistemicorp

@bryevdv bryevdv added this to the short-term milestone Sep 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants