vaadin / vaadin-grid-flow

Vaadin Flow Java API for vaadin/vaadin-grid Web Component
https://vaadin.com/components/vaadin-grid
Other
14 stars 16 forks source link

scrollTo methods for Grid & TreeGrid #327

Closed bisam-rd closed 4 years ago

bisam-rd commented 6 years ago

Hello,

A feature for our application with Vaadin10 is to create a new line in a TreeGrid by clicking on a button.

The problem is if the tree grid has many lines, the user can't see the new line he created, so we want to scroll programatically when the line is created. To achieve this purpose, we call javascript function "_scrollToIndex" after line creation and expansion. That's doesn't work because javascript function "_effectiveSizeChanged" is called after "_scrollToIndex" and reset scroll to 0. Problem seems to happen when there is a hierarchy in the tree grid.

Here is a sample to reproduce the problem.

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.data.provider.hierarchy.TreeData;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.lumo.Lumo;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;

@Route("treeAutoscroll")
@Theme(Lumo.class)
public class TreeAutoscrollView extends Div {

    public TreeAutoscrollView() {

        addTreeGridWithHierarchy();
        addTreeGridWithoutHierarchy();
    }

    public void addTreeGridWithHierarchy() {
        AtomicInteger lastAdded = new AtomicInteger(12);

        TreeGrid<String> treeGrid = new TreeGrid();
        treeGrid.getStyle().set("heigth", "100px");
        treeGrid.addColumn(value -> value).setHeader("Tree Grid With Hierarchy");

        TreeData<String> treeData = new TreeData<>();

        String rootItem = "0";
        treeData.addRootItems(rootItem);
        for (int i = 1; i < lastAdded.get(); i++) {
            String nextItem = String.valueOf(i);
            treeData.addItem(rootItem, nextItem);
            rootItem = nextItem;
        }

        treeGrid.setTreeData(treeData);
        treeGrid.expandRecursively(Collections.singletonList("0"), 1000);
        add(treeGrid);

        add(new Button("Add line on tree grid with hierarchy", event -> {
            String newItem = String.valueOf(lastAdded.getAndIncrement());
            treeData.addRootItems(newItem);
            treeGrid.getDataProvider().refreshAll();
            treeGrid.getSelectionModel().select(newItem);
            treeGrid.expandRecursively(Collections.singletonList("0"), 1000);
            treeGrid.getElement().callFunction("_scrollToIndex", lastAdded.get());
        }));
    }

    public void addTreeGridWithoutHierarchy() {
        AtomicInteger lastAdded = new AtomicInteger(12);

        TreeGrid<String> treeGrid = new TreeGrid();
        treeGrid.getStyle().set("heigth", "100px");
        treeGrid.addColumn(value -> value).setHeader("Tree Grid Without Hierarchy");

        TreeData<String> treeData = new TreeData<>();

        String rootItem = "0";
        treeData.addRootItems(rootItem);
        for (int i = 1; i < lastAdded.get(); i++) {
            String nextItem = String.valueOf(i);
            treeData.addRootItems(nextItem);
        }

        treeGrid.setTreeData(treeData);
        treeGrid.expandRecursively(Collections.singletonList("0"), 1000);
        add(treeGrid);

        add(new Button("Add line on tree grid without hierarchy", event -> {
            String newItem = String.valueOf(lastAdded.getAndIncrement());
            treeData.addRootItems(newItem);
            treeGrid.getDataProvider().refreshAll();
            treeGrid.getSelectionModel().select(newItem);
            treeGrid.expandRecursively(Collections.singletonList("0"), 1000);
            treeGrid.getElement().callFunction("_scrollToIndex", lastAdded.get());
        }));
    }

} 
pleku commented 6 years ago

Calling a private JS API (notice the underscore in the method name) of the web component is not something that we provide guarantee for with Flow. Thus this issue is not really a bug - but an enhancement for Grid or TreeGrid to provide an API in server side to scroll to the last row (and maybe also to the beginning). The web component itself is infinite scrolling, but we do know the size of the grid in the server side as it is reported by the data provider.

We will take a look at making this possible quite soon as this use case is quite common. Adding it should be backportable to the Flow 1.0 version (V10 & V11) of the grid, but we cannot guarantee that yet. I've milestoned it for 1.3 version (latest is 1.2).

denis-anisimov commented 5 years ago

See detailed comments in spike ticket vaadin/vaadin-grid-flow#336.

I copy a part from there which is specific to this ticket:

the reason of this behavior is asynchronous nature of data update. So it's totally fine to call _scrollToIndex as a JS function in the way described in the ticket. But data provider update calls chain should be taken into account.

Here is what happens in the click listener on plain Grid or TreeGrid with no hierarchical data:

Here is what happens with hierarchical data:

As a result:

What does it mean:

pleku commented 5 years ago

As in FW8, the scrollTo, scrollToStart and scrollToEnd, work the same way regardless of the data (hierarchical or not). We will for now just go for the same implementation, which means that the row that is scrolled will be determined by the current state of the tree, meaning that expanding/collapsing nodes will change the row that is scrolled to on the same index.

We should override the methods in TreeGrid though to give improve the javadocs by explaining the behavior of the methods with hierarchical data.

gsedlacz commented 5 years ago

Workaround:

public class MyGrid extends Grid {
    /* ... */
    private void scrollTo(final TableItem item) {
        final int itemIndex = dataProvider.getItemListFiltered().indexOf(item);
        if(itemIndex != -1) {
            getUI().ifPresent(ui -> ui.getPage().executeJs("document.getElementById('" + getId() + "')._scrollToIndex(" + itemIndex + ")"));
        }
    }
}
Legioth commented 5 years ago

A small note about the workaround: you don't need to rely on getId(). Instead, you can do getElement().callFunction("_scrollToIndex", itemIndex).

It should also be noted that any client-side method prefixed with _ should be considered as private API that might change at any time without warning.

rmuller commented 5 years ago
...
public class MyGrid extends Grid {
        final int itemIndex = dataProvider.getItemListFiltered().indexOf(item);
        ....
}

Can you explain this piece of code. Where does getItemListFiltered() come from?

gsedlacz commented 5 years ago
...
public class MyGrid extends Grid {
        final int itemIndex = dataProvider.getItemListFiltered().indexOf(item);
        ....
}

Can you explain this piece of code. Where does getItemListFiltered() come from?

Sure thing.

I just noticed that this dataprovider just extends a ListDataProvider and implements the method getItemListFiltered itself. The class also overrides setFilter to gain access to the SerializablePredicate and stores it as a memmber. If you have that pice of information you can simply call getItems().stream().filter(serializablePredicate).collect(Collectors.toList()) to get the result you are looking for.

TatuLund commented 4 years ago

Should be this closed by https://github.com/vaadin/vaadin-grid-flow/pull/778 ?

tomivirkki commented 4 years ago

@TatuLund yes, indeed. Closing.

Enerccio commented 4 years ago

there is still missing variant with scroll to item...

Legioth commented 4 years ago

Scrolling to item is discussed in https://github.com/vaadin/vaadin-grid-flow/issues/904.