[grid] Calling `recalculateColumnWidths` method automatically scrolls the grid horizontally to the starting position
Description
I've listened to scroll events and added my own logic with something like grid.$.table.addEventListener('scroll', grid.recalculateColumnWidths()).
This causes the grid columns with auto-width to update.
Now, when I scroll the grid vertically, the code works as expected without any issues.
While scrolling the grid horizontally (I have a large table so have a horizontal scrollbar on the grid), the following happens:
Expected outcome
I wish the grid to scroll horizontally to my desired location and stay fixed.

Actual outcome
The grid scrolls back to the starting position and doesn't stick to the location where I scrolled the grid.

In the above image, the grid has scrolled automatically to this starting position from my desired location.
Browsers Affected
- [x] Chrome
- [x] Firefox
@tomivirkki @web-padawan
Confirmed, can be reproduced using the demo. https://cdn.vaadin.com/vaadin-grid/5.6.10/demo/#grid-columns-demos
Workaround:
grid.$.table.addEventListener('scroll', () => {
const scrollLeft = grid.$.table.scrollLeft;
grid.recalculateColumnWidths()
grid.$.table.scrollLeft = scrollLeft;
});
I recommend using some kind of throttling for this however, as running recalculateColumnWidths on every single scroll event would negatively affect scrolling performance.
Workaround:
grid.$.table.addEventListener('scroll', () => { const scrollLeft = grid.$.table.scrollLeft; grid.recalculateColumnWidths() grid.$.table.scrollLeft = scrollLeft; });I recommend using some kind of throttling for this however, as running
recalculateColumnWidthson every single scroll event would negatively affect scrolling performance.
@tomivirkki I'm afraid it won't work. Because what happens is:
- Let's say I scroll to a horizontal position to 480px.
- The scroll event fires
- We hold the value of
scrollLeft = 480 grid.recalculateColumnWidths()will work as intended and will scroll back the grid to its horizontal starting point i.e. 0- We set
grid.$.table.scrollLeft = 480
Now, this is where the problem begins:
- As the
grid.recalculateColumnWidths()is buggy, when it gets executed, it scrolls the grid from 480 to 0 itself, what this does is, triggers the scroll event again.
Now,
- The scroll event fires
- We hold the value of
scrollLeft = 0 grid.recalculateColumnWidths()will work as intended and will scroll back the grid to its horizontal starting point i.e. 0- We set
grid.$.table.scrollLeft = 0
This way, the problem still remains.
Maybe you can set a flag to disable the listener logic while recalculate is being run:
- horizontal position to 480px;
- scroll event fires
- enable the the flag
myFlag = true; - We hold the value of scrollLeft = 480
- grid.recalculateColumnWidths()
- it fires a scroll event but the listener logic is not run again because it checks for the flag
- We set grid.$.table.scrollLeft = 480
- disable the flag
myFlag = false;
@tomivirkki :stop_sign: Ooooops! It didn't work! :cry:
Because, I'm using LitElement and as far as I understand, LitElement performs batch rendering to the updated props. So, in that case,
First batch rendered animation frame:
- horizontal position to 480px;
- scroll event fires
- enable the the flag
myFlag = true; - We hold the value of scrollLeft = 480
- grid.recalculateColumnWidths()
- it fires a scroll event, but the scroll event happens in the next animation frame(event loop), not in this loop
- We set grid.$.table.scrollLeft = 480
- disable the flag
myFlag = false;
Second batch animation frame:
- The queued scroll event from the First batch section fires.
- Sees the flag
myFlag = false, so our flag logic here won't work as the subsequent chunk of code fires again.
Ah, I think what's going on. We're probably using different grid versions. With the newest version, the original workaround works just fine. See it running live at https://productive-neat-manuscript.glitch.me/ (source: https://glitch.com/edit/#!/productive-neat-manuscript)
We have the same issue when using column width recalculation on a column sort. The snippet from Tomi helped to solve the issue in that case.
Update on this issue. Using recalculateCW with auto width columns can lead to mispositioned headers:
Not sure, if this is part of this issue or something different, but it relates at least to the given workaround
To fix that, another scroll left helps:
let left = this.$.table.scrollLeft;
let top = this.$.table.scrollTop;
this.recalculateColumnWidths();
this.$.table.scrollLeft = left;
this.$.table.scrollTop = top;
this.$.table.scrollLeft = this.$.table.scrollLeft - 1;
Some sample code to reproduce the issue with the left scroll. It also contains the basic workaround, that will keep the scroll position. This one will lead to misaligned headers. It also contains the additional workaround to realign the headers correctly (both commented out)
var grid = new Grid<Integer>();
grid.addThemeVariants(GridVariant.LUMO_COLUMN_BORDERS);
grid.setItems(IntStream.range(0, 500).boxed().collect(Collectors.toList()));
grid.setColumnRendering(ColumnRendering.LAZY);
DataProvider<Integer, ?> dataProvider = grid.getDataProvider();
dataProvider.addDataProviderListener(event -> {
// using this instead of the normal recalc fixes the left jump
// grid.getElement().executeJs("""
// if(this && this.recalculateColumnWidths) {
// let left = this.$.table.scrollLeft;
// let top = this.$.table.scrollTop;
// this.recalculateColumnWidths();
// this.$.table.scrollLeft = left;
// this.$.table.scrollTop = top;
//
// // this fixes the problem with the misaligned headers
// left = this.$.table.scrollLeft;
// this.$.table.scrollLeft = this.$.table.scrollLeft - 0.1;
// setTimeout(() => {
// this.$.table.scrollLeft = left;
// }, 300);
// }""");
grid.recalculateColumnWidths();
});
for (int i = 0; i < 50; i++) {
grid.addColumn(item -> "very very very very very very very very very very very very very very very long string value").setHeader("HELLO WORLD").setAutoWidth(true);
}
add(new Button("Refresh", e -> grid.getDataProvider().refreshAll()), grid);
grid.setSizeFull();
setSizeFull();