Problem with Grid.scrollToItem

Grid.scrollToItem has stopped working for me and I’m trying out to find out why and what I can do about it.

My layout is somewhat complex, and I use custom components, but I’m not sure that is directly relevant.

If I I modify my layout and call scrollToItem in one operation, the scroll doesn’t happen.
If I do it in two separate user actions it does work, so it looks to be a timing issue.

Claude suggested replacing grid.scrollToItem(row); with :

int index = dataStoreInstance.indexOfRow(row);
grid.getElement().executeJs("requestAnimationFrame(() => this.scrollToIndex($0))", index);

I’ve done some light testing, and it does seem to solve my problem.
So, the questions are:

  • Is there any downside to this?
  • Is this what Vaadin’s scrollToItem should’ve done?

Hello! This could be a regression from fix!: ensure only last Grid scroll invocation is sent to client by vursen · Pull Request #8666 · vaadin/flow-components · GitHub. Could you please create a ticket with a reproduction example so we can look into it?

I tried to reproduce with as simple a setup as possible:

  • SplitLayout
    • Grid
    • [detail]

When I click on a row in the grid, I “minimize” the grid by setting the splitter position to, say, 10%, to give the detail more space.

When I use Vaadin’s regular SplitLayout and Grid, it seems to work fine.
When I switch to my vibe-coded PtsmcSplitLayout widget it stops working…

OK. That probably means I can’t blame Vaadin :slight_smile:

Or, can I? This is outside my comfort zone, but it does sound like scrollIntoView should be done after all layout changes, whatever those are. Or is this just part of the wonderful world of web development?

An extra wrinkle is that I have to handle sorted lists, so I copy the original method and only change the “executeJs” call.

That caused an extra problem for TreeGrid.scrollToItem, since it refers to a package-private class. Had to hack around that as well.

I ended up with:

MyGrid extends Grid {
    /*
     * Override Vaadin's version, since it doesn't seem to work when grid is inside our custom SplitLayout and we minimize that.
     * 'requestAnimationFrame' means the scroll is done after the layout has settled
     */
    @Override
    public void scrollToItem(T item) {
        AbstractDataView<T> dataView = getDataProvider().isInMemory()
            ? getListDataView()
            : getLazyDataView();
        int itemIndex = dataView.getItemIndex(item)
            .orElseThrow(() -> new NoSuchElementException(
                "Item to scroll to cannot be found: " + item));

        getElement().executeJs("requestAnimationFrame(() => this.scrollToIndex($0))", itemIndex);
    }
}

MyTreeGrid extends TreeGrid {

    /*
     * Override Vaadin's version, since it doesn't seem to work when grid is inside our custom SplitLayout and we minimize that.
     * 'requestAnimationFrame' means the scroll is done after the layout has settled
     * We also have to do an extra hack to be able to cast to the package-private TreeGridDataCommunicator   
     */
    @Override
    public void scrollToItem(T item) {

        var dataCommunicator = getDataCommunicator();
        var itemKey = dataCommunicator.getKeyMapper().key(item);
        var itemIndexPath = TreeGridDataCommunicatorUnwrapper.resolveItem(dataCommunicator, item);

        scheduleScrollExecution(() -> getElement().executeJs(
                "requestAnimationFrame(() => this.$connector.scrollToItem($0, ...$1))", itemKey,
                itemIndexPath));
        
    }

}

/**
 * This is our class, but placed inside Vaadin's package, so that we can cast to package-private TreeGridDataCommunicator
 */
public class TreeGridDataCommunicatorUnwrapper {

    public static int[] resolveItem(HierarchicalDataCommunicator<?> communicator, Object item) {
        return ((TreeGridDataCommunicator<Object>)communicator).resolveItem(item);

    }
    
}