Last active
April 23, 2024 10:19
-
-
Save TatuLund/2cf890b9dd130bbcb9123bb3c7a5bbff to your computer and use it in GitHub Desktop.
Example for Vaadin 23 to workaround issues with scrollToIndex in TreeGrid. Note, in Vaadin 24 there is new hierarchical scrollToIndex method, and this example does not work with the latest Vaadin 24.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.vaadin.tatu; | |
| import java.util.Collections; | |
| import java.util.LinkedList; | |
| import com.vaadin.flow.component.ClientCallable; | |
| import com.vaadin.flow.component.button.Button; | |
| import com.vaadin.flow.component.html.Div; | |
| import com.vaadin.flow.component.textfield.TextField; | |
| import com.vaadin.flow.component.treegrid.TreeGrid; | |
| import com.vaadin.flow.data.provider.hierarchy.TreeData; | |
| import com.vaadin.flow.data.provider.hierarchy.TreeDataProvider; | |
| import com.vaadin.flow.router.Route; | |
| import elemental.json.JsonValue; | |
| @Route("treegrid") | |
| public class TreeGridScrollingEnhancedPage extends Div { | |
| TreeGrid<String> grid = new TreeGrid<>(); | |
| TreeData<String> treeData = new TreeData<>(); | |
| private Integer index = 0; | |
| public TreeGridScrollingEnhancedPage() { | |
| // Grid with single column | |
| grid.addHierarchyColumn(item -> item); | |
| // Dummy data | |
| treeData.addRootItems("A", "B", "C", "D", "E"); | |
| treeData.addItems("A", "A1", "A2", "A3"); | |
| treeData.addItems("A3", "A3-1", "A3-2"); | |
| treeData.addItems("A3-1", "A3-1-1", "A3-1-2", "A3-1-3"); | |
| treeData.addItems("B", "B1", "B2", "B3"); | |
| treeData.addItems("C", "C1", "C2", "C3", "C4"); | |
| treeData.addItems("D", "D1", "D2"); | |
| treeData.addItems("D2", "D2-1", "D2-2", "D2-3", "D2-4"); | |
| treeData.addItems("D2-1", "D2-1-1", "D2-1-2", "D2-1-3"); | |
| treeData.addItems("E", "E1", "E2", "E3", "E4", "E5"); | |
| treeData.addItems("E2", "E2-1", "E2-2", "E2-3", "E2-4"); | |
| treeData.addItems("E4", "E4-1", "E4-2", "E4-3"); | |
| treeData.addItems("E4-1", "E4-1-1", "E4-1-2", "E4-1-3", "E4-1-4"); | |
| treeData.addItems("E4-2", "E4-2-1", "E4-2-2", "E4-2-3"); | |
| treeData.addItems("E4-3", "E4-3-1", "E4-3-2"); | |
| treeData.addItems("E5", "E5-1", "E5-2"); | |
| treeData.addItems("E5-2", "E5-2-1", "E5-2-2", "E5-2-3"); | |
| TreeDataProvider<String> provider = new TreeDataProvider<>(treeData); | |
| grid.setDataProvider(provider); | |
| // Delayed scrolling from the cookbook example | |
| grid.getElement() | |
| .executeJs("this.scrollWhenReady = function(index, firstCall){" | |
| + "if(this.loading || firstCall) {var that = this; setTimeout(function(){that.scrollWhenReady(index, false);}, 200);}" | |
| + " else {this.scrollToIndex(index);}" + "};"); | |
| grid.getElement().executeJs( | |
| "function debounce(func, wait) {let timeout; return () => {if (timeout) {clearTimeout(timeout);} timeout = setTimeout(func, wait);}} const onScroll = debounce(() => $0.$server.firstIndex($1._firstVisibleIndex), 100); $1.$.table.addEventListener('scroll', onScroll);", | |
| getElement(), grid.getElement()); | |
| TextField tf = new TextField("Target Item"); | |
| tf.setValue("E4-2-1"); | |
| Button button = new Button("Scroll to item", e -> { | |
| String targetItem = tf.getValue(); | |
| if (treeData.contains(targetItem)) { | |
| expandAndScroll(targetItem); | |
| } | |
| }); | |
| Button refresh = new Button("Refresh", e -> { | |
| int savedIndex = index; | |
| provider.refreshAll(); | |
| System.out.println("scrollTo: " + savedIndex); | |
| this.getElement().executeJs("return;").then(result -> { | |
| grid.scrollToIndex(savedIndex); | |
| }); | |
| }); | |
| add(tf, button, refresh, grid); | |
| } | |
| @ClientCallable | |
| private void firstIndex(JsonValue index) { | |
| this.index = Integer.valueOf(index.toJson()); | |
| System.out.println("firstIndex: " + this.index); | |
| } | |
| void expandAndScroll(String targetItem) { | |
| LinkedList<String> path = getTreePath(targetItem, new LinkedList<>()); | |
| Collections.reverse(path); | |
| path.add(targetItem); | |
| // Row index of the item with fully expanded root item | |
| int itemFlatIndex = expandAndCount(path); | |
| // Calculate the total number of visible items till the root of target | |
| // item | |
| int totalItems = treeData.getRootItems() | |
| .subList(0, treeData.getRootItems().indexOf(path.get(0))) | |
| .stream().map(this::countItems).reduce(Integer::sum).orElse(0); | |
| grid.getElement().executeJs("this.scrollWhenReady($0, true);", | |
| totalItems + itemFlatIndex); | |
| } | |
| // Count child nodes for item | |
| int countItems(String item) { | |
| int leaves = 0; | |
| if (treeData.getChildren(item).size() == 0 || !grid.isExpanded(item)) { | |
| return 1; | |
| } else { | |
| // ADD PARENT ITEM | |
| leaves += 1; | |
| } | |
| for (int i = 0; i < treeData.getChildren(item).size(); i++) { | |
| leaves += countItems(treeData.getChildren(item).get(i)); | |
| } | |
| return leaves; | |
| } | |
| // Getting the index of target item and expanding nodes on the way to it | |
| int expandAndCount(LinkedList<String> path) { | |
| int totalCount = 0; | |
| for (int i = 0; i < path.size(); i++) { | |
| if (!grid.isExpanded(path.get(i))) { | |
| grid.expand(path.get(i)); | |
| } | |
| if (i + 1 < path.size()) { | |
| totalCount += treeData.getChildren(path.get(i)) | |
| .indexOf(path.get(i + 1)) + 1; | |
| } | |
| } | |
| return totalCount; | |
| } | |
| // Generating list of parents from the item till root | |
| LinkedList<String> getTreePath(String item, LinkedList<String> parents) { | |
| String parent = treeData.getParent(item); | |
| if (parent != null) { | |
| parents.add(parent); | |
| return getTreePath(parent, parents); | |
| } | |
| return parents; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment