-
-
Notifications
You must be signed in to change notification settings - Fork 74
Issue 777 - Align column headers when using fixed rows #793
Conversation
- apply style to both the last row and the last `th` row
// For some reason, some browsers require the size to be set explicitly on the header cells too | ||
Array.from<HTMLElement>( | ||
lastTrOfThs.children as any | ||
).forEach((c, i) => this.setCellWidth(c, widths[i])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firefox requires th
cells to be fixed alongside tr
cells for the table to behave consistently.
@@ -33,7 +33,7 @@ | |||
"private::test.python": "python -m unittest tests/unit/format_test.py", | |||
"private::test.unit": "cypress run --browser chrome --spec 'tests/cypress/tests/unit/**/*'", | |||
"private::test.standalone": "cypress run --browser chrome --spec 'tests/cypress/tests/standalone/**/*'", | |||
"build.watch": "webpack-dev-server --content-base dash_table --mode development --config webpack.dev.config.js", | |||
"build.watch": "webpack-dev-server --disable-host-check --content-base dash_table --mode development --config webpack.dev.config.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Browserstack, when running on iOS devices, localhost
is redirected to bs-local
which webpack-dev-server doesn't like, this flag, as its name indicates, makes it not care
r0c1.style.marginLeft = `-${width}px`; | ||
r0c1.style.marginRight = `${width}px`; | ||
r1c1.style.marginLeft = `-${width}px`; | ||
r1c1.style.marginRight = `${width}px`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marginRight
caused the table to be smaller than expected with fixed columns. Removing it for both r0c1
and r1c1
fragments ensures the table is the expected size
// If the table was resized and isn't in a cycle, re-run `handleResize`. | ||
// If the final size is the same as the starting size from the previous iteration, do not | ||
// resize the main table, instead just use as is, otherwise it will oscillate. | ||
if (nextWidth !== currentWidth) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested these for scenarios involving decimal places and is seems to behave fine on all important targets, decimals or not. There doesn't seem to be a need for an epsilon
.
- remove `forcedResizeOnly` state prop - add sizing test
style_header, | ||
style_header_conditional, | ||
style_table | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only resize through React pipeline if a style prop has changed (memoized: https://github.com/plotly/dash-table/pull/793/files#diff-a6637ea69a08d602c311a574b4e4979aR324). This behaves similarly to the 🔪 forcedResizedOnly
state prop, but also allows style changes to trigger recalculation. For example display:none;
, changes in column widths, etc.
|
||
tr.style.height = getComputedStyle(tr2).height; | ||
}); | ||
this.handleResize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If executing this, the styles have changed, the above clears up all forced width before handling the resize, allowing for the table to resize itself correctly.
// Not pixels -> not displayed | ||
if (!/\d+px/.test(currentTableWidth)) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If not px
, the table is not rendered, don't resize it. Lucky plotly/plotly.js#4925 happened at the same time, otherwise it would have gone unnoticed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OIC, '100.1px'
will in fact pass this test as it's not a whole string match. Worried me at first but it should work.
|
||
def cells_are_same_width(target, table): | ||
wait.until(lambda: abs(target.size["width"] - table.size["width"]) <= 1, 3) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helper function that checks if two tables that are expected to have the same size / cell sizes are identical (or nearly identical) in size
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This routine is testing the last row of cells; while we're here would it be worthwhile checking that all the rows of a single table are the same, ie rows aren't ragged? That's certainly been a problem in the past...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jagged rows happen when we transition from one fragment to another of the DataTable. Checking the cells of any row of a fragment against the cells of any row of the .cell-1-1
fragment should be sufficient if the table doesn't have a fixed portion.
What I realize from this comment is that for szng002
I should also be testing the target
against itself.
fixed_columns=dict(headers=True, data=1), | ||
fixed_rows=dict(headers=True, data=1), | ||
), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create all combinations of fixed columns/rows tables and hide them.
style_table=dict( | ||
width=750, minWidth=750, maxWidth=750, paddingBottom=10 | ||
) | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tables are used to make sure the test tables are behaving as expected when updated through the callback below
tests/selenium/test_sizing.py
Outdated
return [ | ||
dict(width=350, minWidth=350, maxWidth=350, paddingBottom=10) | ||
for i in variations_range | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the table style to make them visible / hidden and/or resized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful test :)
Looks like you started to collect all these style_table
dicts up above in styles
then changed tacks and put them inline down here... could probably be simplified a little, also we don't really need n clones, could just be return [dict(...)] * variations_range
... but 🤷 not important for a test, the functionality is great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test did change a lot between this version and the first implementation. Cleaned it up. 575aa2c
|
||
target = test.driver.find_element_by_css_selector("#table0") | ||
|
||
for i in range(1, len(variations)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test various configurations of fixed columns/rows and merge_duplicate_headers to make sure they all render the same with % based column widths.
|
||
ths.forEach(this.clearCellWidth); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force all th
rows with all cells to match the expected width.
const tr = th.parentElement as HTMLElement; | ||
this.resetFragmentCells(r0c0); | ||
this.resetFragmentCells(r0c1); | ||
this.resetFragmentCells(r1c0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor - skip reset for a table that doesn't exist? Also seems like this could be 🌴
[r0c0, r0c1, r1c0].forEach(rc=> {
rcTable=...;
if (rcTable) { rcTable.style.width = ''; this.resetFragmentCells(rc); }
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/selenium/test_sizing.py
Outdated
|
||
variations_range = range(0, len(variations)) | ||
|
||
tables = [DataTable(**variation) for i, variation in enumerate(variations)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tables = [DataTable(**variation) for i, variation in enumerate(variations)] | |
tables = [DataTable(**variation) for variation in variations] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/selenium/test_sizing.py
Outdated
"name": [ | ||
"A-----------------VERY LONG", | ||
"B-----------------VERY LONG", | ||
"C-----------------VERY LONG", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to have the last row of names all be different lengths, so we know it's not just the content match giving rise to the width match?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/selenium/test_sizing.py
Outdated
{"_": 0, "a": 809, "b": 591, "c": 511}, | ||
], | ||
style_table=dict(width=500, minWidth=500, maxWidth=500, paddingBottom=10), | ||
style_cell=dict(width="20%", minWidth="20%", maxWidth="20%"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only see 4 columns - is it intentional to have <100% total width?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexcjohnson Addressed all previous comments. Also added b02a022 to fix #780 and lock behavior with new tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic!💃
Fixes #777
Fixes #780
Under certain circumstances, the table fragments were misaligned for scenarios involving
fixed_rows
andfixed_columns
. Also, some visual tests were already showing that the behavior was incorrect but it was hard to spot unless zooming in. For that reason I believe the tests already appropriately cover the cases for this PR and that no additional tests are required, only more caution.A few problems were encountered:
Certain browsers require both the
tr
and theth
rows to have their size explictly set when forcing them through styles. This happens for fixed row/columns using thedata
nested field. For example, in Firefox, forcing atr
to have a width of200px
can be overriden by a previousth
requiring more space. Theth
"wins". The fix for this is to both set thetr
andth
cell size for the last row of each group.The implementation was subtly wrong, using the size of the wrong fragment to coerce another. I think some of those is due to historical reasons, and were not reworked correctly when the table was reworked to allow variable row heights in Issue 649 - Fix mismatched row height for fixed columns #722.
Certain browsers, for certain tables will not be able to converge on a single table width, but will instead oscillate between two very close values. As far as I know always distant by
1px
. The "iterations" code for the table now checks for cycles.Also, a nice side-effect of this PR is that tables with fixed columns now behave more consistently vs. unfixed ones. Will need to check the documentation for any mention of incorrect width that will have to be removed.
![image](https://user-images.githubusercontent.com/8092993/84706772-8cdd7680-af2b-11ea-9b2c-08d830ec2897.png)
![image](https://user-images.githubusercontent.com/8092993/84706882-bf876f00-af2b-11ea-9a93-0226f1df1187.png)
now looks like this fixed and unfixed: