Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Row IDs #412

Merged
merged 23 commits into from
Apr 26, 2019
Merged

Row IDs #412

merged 23 commits into from
Apr 26, 2019

Conversation

alexcjohnson
Copy link
Collaborator

@alexcjohnson alexcjohnson commented Apr 21, 2019

Row ID support - closes #312

  • data - each row can have id key, used as row id (can also be displayed if there’s a column with id='id')
  • active_cell -> {row, column, row_id, column_id}
  • selected_cells -> array like active_cell
  • selected_rows -> added selected_row_ids
  • start_cell and end_cell -> these actually didn't seem to be wired up? Now they look like active_cell
  • derived -> added
    • derived_viewport_row_ids
    • derived_virtual_row_ids
    • derived_virtual_selected_row_ids
    • derived_viewport_selected_row_ids

Still need to add tests, but AFAICT it works as intended.

Simple test app with pagination (so at least viewport indices are different) and row selection & deletion:

import json
import pandas as pd

import dash
import dash_html_components as html
from dash_table import DataTable
from dash.dependencies import Input, Output

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
data = df.to_dict("rows")
for row in data:
    row['id'] = 'id:' + row['State'].lower()

app = dash.Dash(__name__)
app.layout = html.Div([
    DataTable(
        id='main',
        columns=[{'name': i, 'id': i} for i in df.columns],
        data=data,
        row_selectable='multi',
        row_deletable=True,
        pagination_settings={'page_size': 3, 'current_page': 0}
    ),
    html.Hr(),
    DataTable(
        id='out',
        columns=[{'name': 'prop', 'id': 'prop'}, {'name': 'val', 'id': 'val'}],
        data=[],
        style_cell_conditional=[
            {'if': {'column_id': 'val'}, 'textAlign': 'left'}
        ]
    )
])


props = [
    'active_cell', 'start_cell', 'end_cell', 'selected_cells',
    'selected_rows', 'selected_row_ids',
    'derived_viewport_indices', 'derived_viewport_row_ids',
    'derived_virtual_indices', 'derived_virtual_row_ids',
    'derived_viewport_selected_rows', 'derived_viewport_selected_row_ids',
    'derived_virtual_selected_rows', 'derived_virtual_selected_row_ids'
]


@app.callback(Output('out', 'data'), [Input('main', v) for v in props])
def show_props(*args):
    return [
        {'prop': prop, 'val': json.dumps(val)}
        for prop, val in zip(props, args)
    ]

if __name__ == '__main__':
    app.run_server(debug=True)

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 21, 2019 23:11 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 00:08 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 00:22 Inactive
I was a bit schizophrenic about this before...
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 01:04 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 01:30 Inactive
@chriddyp
Copy link
Member

🌟 Looks pretty clean!

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 04:43 Inactive
@@ -1,6 +1,4 @@
import collections
import inspect
import sys
Copy link
Collaborator Author

@alexcjohnson alexcjohnson Apr 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dunno why, but locally npm run lint asked me to make the changes in this commit, even though on CI it passes 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running the same versions? I've found that the default rules are updated frequently..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh actually npm run lint excluded a whole bunch of rules that we really don't need to exclude (as long as we're excluding the auto-generated files, which we are). I'll un-exclude them. Probably worth taking a look at this in other repos as well... it makes sense to have looser linting in tests but some of these rules are useful - like unused imports, can improve perf, only files setting up namespaces have any reason to include them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stricter linting -> 8f01c25

let endCol = end_cell && end_cell.column;

if (selectingDown) {
if (active_cell.row > minRow) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the logic here to be explicitly rectangular - ie picking out the region edges, editing these, and then recreating the selected_cells list from that at the end - rather than directly manipulating selected_cells as you had previously. I did this because it seemed both clearer and faster, I needed the new edges anyway for end_cell, and this way selected_cells will always be ordered as it appears on screen.

I considered changing these conditions from active_cell to start_cell. These are only different if you move active_cell after making the selection (via tab or enter), and as it stands some weird things can happen to the selection if you alter it via shift-arrow at that point - it's hard to predict whether shift-down will add a row at the bottom or remove one at the top. If I used start_cell instead of active_cell here, the cell you first clicked on would always be a corner of the selection, and shift-arrow would move the opposite corner. But there are two problems with that approach: (1) after you've moved active_cell, there's no visual indicator of which corner you started on, and (2) it's possible to shrink the selection so active_cell is no longer part of the selection. So in the end I left it with active_cell, but we could revisit that.

const newStartCol = endCol === minCol ? maxCol : minCol;

if (startRow !== newStartRow || startCol !== newStartCol) {
newProps.start_cell = makeCell(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updating start_cell only for that strange situation of reshaping the selection after moving active_cell

newProps.selected_rows = R.map(
// all rows past idx have now lost one from their index
i => i > idx ? i - 1 : i,
R.without([idx], selectedRows)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bug previously - delete a row and any selected rows after it kept the same index, not the same row contents.

data: R.remove(idx, 1, data)
data: R.remove(idx, 1, data),
// We could try to adjust selection, but there are lots of edge cases
...clearSelection
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously we left selected_cells intact when deleting a row, and only cleared active_cell if it was actually deleted. But that has some problems, and for simplicity and symmetry with deleteColumns I decided to just clear the selection here too.

@@ -21,15 +23,15 @@ function getBackEndPagination(
return {
loadNext: () => {
pagination_settings.current_page++;
setProps({ pagination_settings });
setProps({ pagination_settings, ...clearSelection });
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing cell selection on page changes. You can select rows on separate pages and have them stay selected... but that's with row-by-row checkboxes, an inherently stickier interaction that feels right to persist even when a checked box is on a hidden page.

* And when you've selected multiple cells the browser selection is
* completely wrong.
*/
window.getSelection().removeAllRanges();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, shift-click to select in the first example at https://dash.plot.ly/datatable:
Screen Shot 2019-04-22 at 1 43 56 AM

@@ -1,57 +1,69 @@
import * as R from 'ramda';
import { SelectedCells, ICellFactoryProps } from 'dash-table/components/Table/props';
import { min, max, set, lensPath } from 'ramda';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a couple of forays into specific ramda imports - my local linter complains about * as R and as @chriddyp pointed out (somewhere?) we may get a lighter build that way too.

Copy link
Contributor

@Marc-Andre-Rivet Marc-Andre-Rivet Apr 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Depending on your linter / editor's capacity to handle tsconfig, it may or may not handle the typescript option for * as R. This is allowed because of "allowSyntheticDefaultImports": true, in tsconfig.base.json.
The way imports are used here webpack should prune the unused content automagically but this is fine too.

minRow: min(row, start_cell.row),
maxRow: max(row, start_cell.row),
minCol: min(col, start_cell.column),
maxCol: max(col, start_cell.column)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hmm, here I used start_cell and not active_cell like I did in the keypress handler... and indeed it can have weird effects like active_cell becoming outside the selection. So one of these should change... which one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

standardized on active_cell like keypresses #412 (comment) -> 1c69cfb

} = propsFn();

const selected = isCellSelected(selected_cells, idx, i);
const selected = isSelected(selected_cells, idx, i);

// don't update if already selected
if (selected) {
return;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW in case of shift-click I think this return is problematic - it means you can't shrink a selection without doing something hacky like first clicking over to the other side of the starting point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better shift-click behavior -> 1c69cfb (and 3e31a7f)

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 22, 2019 06:43 Inactive
*/
active_cell: PropTypes.array,
active_cell: cellCoordsIDsProp,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the resulting metadata.json is correct b/c the value is defined in the same file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh good call... looks like I can't 🌴 this in the existing react-docgen implementation ☹️

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

back to explicit prop types -> 397b914

@@ -64,7 +96,7 @@ storiesOf('DashTable/With Data', module)
]}
/>));

const columns = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
const columnsA2J = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does A2J stand for Alex Johnson was here? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha I almost called it columnsAJ but thought that might be too selfish. It's supposed to be just 'a' -> 'j'

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 23, 2019 21:57 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 23, 2019 23:15 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 24, 2019 22:52 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 25, 2019 00:00 Inactive
@alexcjohnson
Copy link
Collaborator Author

@Marc-Andre-Rivet ready for review. I didn't change sorting_settings (#412 (comment)), would be happy to do that here or in a separate PR.

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 25, 2019 15:26 Inactive
@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 26, 2019 14:04 Inactive
@@ -22,7 +22,7 @@
"private::host_dash8083": "python tests/cypress/dash/v_fe_page.py",
"private::host_js": "http-server ./dash_table -c-1 --silent",
"private::lint:ts": "tslint '{src,demo,tests}/**/*.{js,ts,tsx}' --exclude '**/@Types/*.*'",
"private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py --ignore=E501,F401,F841,F811,F821 dash_table",
"private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning that up / removing the exceptions

// there should always be an active_cell if we got here...
// but if for some reason there isn't, bail out rather than
// doing something unexpected
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is unexpected, maybe add a Logger.warning here

@chriddyp chriddyp temporarily deployed to dash-table-review-pr-412 April 26, 2019 17:03 Inactive
Copy link
Contributor

@Marc-Andre-Rivet Marc-Andre-Rivet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good to me 💃 Thanks for all the additional clean up.

@alexcjohnson alexcjohnson merged commit 6aa16df into master Apr 26, 2019
@alexcjohnson alexcjohnson deleted the row-ids branch April 26, 2019 19:56
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Row ID Support [Sponsored: Due March 1]
3 participants