web-components icon indicating copy to clipboard operation
web-components copied to clipboard

[grid] Calling `recalculateColumnWidths` method automatically scrolls the grid horizontally to the starting position

Open NadeemShakya opened this issue 5 years ago • 9 comments

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.

image

Actual outcome

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

image

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

NadeemShakya avatar Oct 05 '20 06:10 NadeemShakya

Confirmed, can be reproduced using the demo. https://cdn.vaadin.com/vaadin-grid/5.6.10/demo/#grid-columns-demos

web-padawan avatar Oct 05 '20 08:10 web-padawan

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.

tomivirkki avatar Oct 05 '20 08:10 tomivirkki

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.

@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.

NadeemShakya avatar Oct 05 '20 08:10 NadeemShakya

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 avatar Oct 05 '20 09:10 tomivirkki

@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.

NadeemShakya avatar Oct 06 '20 03:10 NadeemShakya

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)

tomivirkki avatar Oct 06 '20 08:10 tomivirkki

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.

stefanuebe avatar Oct 24 '22 13:10 stefanuebe

Update on this issue. Using recalculateCW with auto width columns can lead to mispositioned headers:

image

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;

stefanuebe avatar Jun 05 '24 09:06 stefanuebe

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();

stefanuebe avatar Jun 05 '24 12:06 stefanuebe