plotly / dash-table

OBSOLETE: now part of https://github.com/plotly/dash
https://dash.plotly.com
MIT License
420 stars 72 forks source link

[BUG] navigating via keyboard prevents table from updating #657

Open vivekvs1 opened 4 years ago

vivekvs1 commented 4 years ago

Hi there,

Another rather important bug that I thought should bring up:

cell_update

See above gif.

Trial 1: I have backend code to update the value of 'Ht Night' to be the same as 'Ht Day'. When I refresh page and give values to 'Ht Day',

  1. Using Keyboard Shortcuts: moving to the adjacent cell/ press enter - updates the 'Ht Night' correctly
  2. Clicking on any other cell updates 'Ht Night correctly.

Trial 2 When I change any dropdown, or click some random cells in the table; and then change the values of 'Ht Day':

    1. Using Keyboard Shortcuts: moving to the adjacent cell/ press enter - does not update the 'Ht Night' correctly
  1. Clicking on any other cell updates 'Ht Night correctly.

I was rechecking the backend and all sorts of errors to see if some error was going uncaught. But now I am sure its a bug, since using clicks to change focus of the cells seems to update the table correctly.

@alexcjohnson : sorry to bother again - but do you agree that this is a bug? :)

alexcjohnson commented 4 years ago

Can you post some simplified app code to reproduce this?

vivekvs1 commented 4 years ago

Sure. Working on it.

It seems to be somehow connected 'active cell'. I have added comments to issue: https://github.com/plotly/dash-table/issues/652

vivekvs1 commented 4 years ago

Sorry for the delay. I finally figured out what could cause this error. Please see below. When I use active_cell for the table as an input for another component, the values in the table don't update as expected.

cell_update_2

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
from dash.dependencies import State

from layout_lib.table_layouts.worksheet_layout import *
app = dash.Dash(__name__)

layout = html.Div(
    [
        dash_table.DataTable(
            id="table",
            columns=[
                {"name": ["ID"], "id": "source_id",},
                {"name": ["Day"], "type": "numeric", "id": "rec_ht_day",},
                {"name": ["Night"], "type": "numeric", "id": "rec_ht_night",},
                {"name": ["row"], "id": "house_rows", "presentation": "dropdown",},
            ],
            dropdown={
                "house_rows": {
                    "options": [
                        {"label": "0", "value": 0},
                        {"label": "1", "value": 1},
                    ],
                    "clearable": False,
                },
            },
            data=[
                {
                    "source_id": "B20_NF_S",
                    "house_rows": 0,
                    "rec_ht_day": 3,
                    "rec_ht_night": 3,
                    "notes": "This is a test memo",
                },
                {"source_id": "____B20_NF_S"},
            ],
            editable=True,
        ),
        dcc.Textarea(id="worksheet_notes",),
    ],
)

app.layout = layout

@app.callback(
    Output("table", "data"),
    [Input("table", "data_timestamp"),],
    [State("table", "data"), State("table", "data_previous"),],
)
def worksheet_operations(
    _table_ts, data, data_previous,
):
    df = pd.DataFrame(data)
    df_prev = pd.DataFrame(data_previous)
    df = df.fillna("-")
    df_prev = df_prev.fillna("-")

    if not df.equals(df_prev):
        status = 1 - df.eq(df_prev)
        status["decision"] = status.sum(axis=1)
        modified_row_id = status.query("decision == True").index.tolist()[0]
        modified_row = status.loc[modified_row_id]
        modified_column_id = modified_row[modified_row == 1].index.to_list()[0]

        if modified_column_id == "rec_ht_day":
            data[modified_row_id]["rec_ht_night"] = data[modified_row_id]["rec_ht_day"]
            return data

        else:
            return data

    else:
        return data

@app.callback(
    [Output("worksheet_notes", "value"), Output("worksheet_notes", "disabled")],
    [Input("table", "active_cell")],
    [State("table", "data")],
)
def worksheet_notes_display(active_cell, data):
    if (
        active_cell is not None
        and not data[active_cell["row"]]["source_id"][:4] == "____"
    ):
        return [data[active_cell["row"]]["notes"], False]
    else:
        return ["", True]

if __name__ == "__main__":
    app.run_server(debug=True)
vivekvs1 commented 4 years ago

@alexcjohnson : This is a bug isn't it? I hope it's not poorer understanding from my end. Thanks.!

alexcjohnson commented 4 years ago

Sorry for the slow reply - yes, I think you're correct that this is a bug. Thanks for the clear reproduction. Indeed, the table data does not update correctly when active_cell is used as a callback input AND you navigate with the keyboard after editing the table.

Here's a simplified app showing the same:

import json

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        dash_table.DataTable(
            id="table",
            columns=[
                {"name": ["Day"], "type": "numeric", "id": "rec_ht_day"},
                {"name": ["Night"], "type": "numeric", "id": "rec_ht_night"},
            ],
            data=[
                {"rec_ht_day": 3, "rec_ht_night": 3},
                {},
            ],
            editable=True,
        ),
        dcc.Textarea(id="worksheet_notes",),
    ],
)

@app.callback(
    Output("table", "data"),
    [Input("table", "data_timestamp")],
    [State("table", "data")],
)
def worksheet_operations(_table_ts, data):
    for row in data:
        row["rec_ht_night"] = row.get("rec_ht_day")

    print(json.dumps(data))
    return data

@app.callback(
    Output("worksheet_notes", "value"),
    [Input("table", "active_cell")],
)
def worksheet_notes_display(active_cell):
    return json.dumps(active_cell)

if __name__ == "__main__":
    app.run_server(debug=True)
vivekvs1 commented 4 years ago

Thanks for acknowledging! I wasn't sure what was going on! :D

vivekvs1 commented 4 years ago

Any updates on this? :)

vivekvs1 commented 4 years ago

If there is anything I can do to help fix this issue, let me know. Thanks