Skip to content

Commit c22b953

Browse files
ugur-vaadinvursen
andauthored
refactor!: expand on scroll to index and throw if not found (#8109)
* feat: add scrolltoitem for treedataproviders * test: remove expand param * test: refactor test page to separate test data * test: move static fields * test: expose test data methods * Revert "refactor: extract logic that will reside in data providers" This reverts commit 57483aa. * refactor: extract data provider logic into methods with same signatures * refactor: cleanup and simplify * Revert "refactor: cleanup and simplify" This reverts commit 3f40845. * test: add unit tests for scrolltoitem * chore: remove demo page * Update vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/treegrid/TreeGridScrollTest.java Co-authored-by: Sergey Vinogradov <[email protected]> * chore: run formatter * refactor: use data provider api and update docs * test: add new providers * chore: remove todo * test: update unit tests for custom data providers * test: fix typo in test name * refactor: move logic to data communicator do not determine item for nested * test: add tests for items that do not belong to the tree * refactor: get data provider once * refactor: extract communicator and expand on scroll to item * chore: fix compilation * refactor: separate resolve item method * Revert "refactor: separate resolve item method" This reverts commit 3425eab. * refactor: expand after index path is resolved * fix: expand before getting index path * refactor: throw exception in communicator and return int array * test: remove lazy provider tests and cleanup * Update vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/treegrid/ScrollToItemTest.java Co-authored-by: Sergey Vinogradov <[email protected]> * Update vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/treegrid/ScrollToItemTest.java Co-authored-by: Sergey Vinogradov <[email protected]> * Update vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/treegrid/ScrollToItemTest.java Co-authored-by: Sergey Vinogradov <[email protected]> * test: cleanup * docs: update table with links * docs: remove link * refactor: expand on scroll to index and throw if not found * chore: run formatter * test: fix it assertion * chore: run formatter * refactor: throw exception in communicator * refactor: use extracted client side scroll method * refactor: remove unnecessary expand * fix: add missing item check for scroll to single index * refactor: do not throw on item not found and support negative indexes --------- Co-authored-by: Sergey Vinogradov <[email protected]>
1 parent 5f82029 commit c22b953

File tree

4 files changed

+177
-25
lines changed

4 files changed

+177
-25
lines changed

vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/treegrid/it/TreeGridScrollToIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public void expandAll_scrollToItem30_1_correctFirstVisibleItem() {
114114
@Test
115115
public void scrollToIndex30_1_correctFirstVisibleItem() {
116116
scrollToIndexInput.sendKeys("30-1", Keys.TAB);
117-
assertFirstVisibleRowContent("Granddad 30");
117+
assertFirstVisibleRowContent("Dad 30/1");
118118
}
119119

120120
@Test

vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/treegrid/TreeGrid.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -956,22 +956,23 @@ public HierarchicalDataProvider<T, SerializablePredicate<T>> getDataProvider() {
956956
* {@link HierarchyFormat#FLATTENED}: the index refers to an item in the
957957
* entire flattened tree, not only the root level, allowing items at any
958958
* expanded level to be reached with this method.
959-
* <p>
960-
* If the index exceeds the valid range, scrolling stops at the last
961-
* available item.
962959
*
963960
* @param index
964961
* zero based index of the item to scroll to
965962
*/
966963
@Override
967964
public void scrollToIndex(int index) {
968-
getUI().ifPresent(
969-
ui -> ui.beforeClientResponse(this, ctx -> getElement()
970-
.executeJs("this.scrollToIndex($0);", index)));
965+
var itemCount = getDataCommunicator().getItemCount();
966+
if (index >= itemCount || index < -itemCount) {
967+
// The index does not correspond to an item
968+
return;
969+
}
970+
doScrollToIndex(index);
971971
}
972972

973973
/**
974-
* Scrolls to a nested item specified by its hierarchical path.
974+
* Scrolls to a nested item specified by its hierarchical path. Any
975+
* collapsed parent of the item is expanded before scrolling.
975976
* <p>
976977
* The hierarchical path is an array of zero-based indexes, where each index
977978
* refers to a child of the item at the previous index. Scrolling continues
@@ -1004,24 +1005,27 @@ public void scrollToIndex(int... path) {
10041005
For HierarchyFormat.FLATTENED, use scrollToIndex(int) with a flat index instead.
10051006
""");
10061007
}
1007-
10081008
if (path.length == 0) {
10091009
throw new IllegalArgumentException(
10101010
"At least one index should be provided.");
10111011
}
1012-
1013-
String joinedIndexes = Arrays.stream(path).mapToObj(String::valueOf)
1014-
.collect(Collectors.joining(","));
1015-
getUI().ifPresent(ui -> ui.beforeClientResponse(this,
1016-
ctx -> getElement().executeJs(
1017-
"this.scrollToIndex(" + joinedIndexes + ");")));
1012+
try {
1013+
var pathItems = ((TreeGridDataCommunicator<T>) getDataCommunicator())
1014+
.getPathItems(path);
1015+
var ancestors = pathItems.subList(0, pathItems.size() - 1);
1016+
expand(ancestors);
1017+
} catch (IllegalArgumentException e) {
1018+
// The path does not correspond to an item
1019+
return;
1020+
}
1021+
doScrollToIndex(path);
10181022
}
10191023

10201024
@Override
10211025
public void scrollToEnd() {
1022-
getUI().ifPresent(ui -> ui.beforeClientResponse(this,
1023-
ctx -> getElement().executeJs(
1024-
"this.scrollToIndex(...Array(10).fill(-1))")));
1026+
var indexes = new int[10];
1027+
Arrays.fill(indexes, -1);
1028+
doScrollToIndex(indexes);
10251029
}
10261030

10271031
/**
@@ -1157,4 +1161,12 @@ public void scrollToItem(T item) {
11571161
scrollToIndex(indexPath);
11581162
}
11591163
}
1164+
1165+
private void doScrollToIndex(int... path) {
1166+
var joinedIndexes = Arrays.stream(path).mapToObj(String::valueOf)
1167+
.collect(Collectors.joining(","));
1168+
getUI().ifPresent(ui -> ui.beforeClientResponse(this,
1169+
ctx -> getElement().executeJs(
1170+
"this.scrollToIndex(" + joinedIndexes + ");")));
1171+
}
11601172
}

vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/treegrid/TreeGridDataCommunicator.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ public int[] resolveItem(T item) {
9797
* @param ancestors
9898
* the ordered list of the ancestors of the item
9999
* @return index path for the given item
100+
* @throws IllegalArgumentException
101+
* if the item does not exist
100102
*/
101103
private int[] getIndexPath(T item, List<T> ancestors) {
102104
var path = new ArrayList<Integer>();
@@ -132,6 +134,38 @@ private List<T> getAncestors(T item) {
132134
return ancestors;
133135
}
134136

137+
/**
138+
* Gets the ordered list of items for the specified path. Returns empty if
139+
* any of the items are not found.
140+
*
141+
* @param path
142+
* the path to get the items for
143+
* @return ordered list of items for the specified path
144+
* @throws IllegalArgumentException
145+
* if the path does not correspond to an item
146+
*/
147+
public List<T> getPathItems(int... path) {
148+
var dataProvider = (HierarchicalDataProvider<T, Object>) getDataProvider();
149+
var pathItems = new ArrayList<T>();
150+
T parent = null;
151+
for (var index : path) {
152+
if (index < 0) {
153+
var childrenCount = dataProvider.getChildCount(
154+
buildQuery(parent, 0, Integer.MAX_VALUE));
155+
index = childrenCount + index;
156+
}
157+
var query = buildQuery(parent, index, 1);
158+
var childOptional = dataProvider.fetchChildren(query).findFirst();
159+
if (childOptional.isEmpty()) {
160+
throw new IllegalArgumentException(
161+
"There is no item with the specified path.");
162+
}
163+
parent = childOptional.get();
164+
pathItems.add(parent);
165+
}
166+
return pathItems;
167+
}
168+
135169
private List<Integer> getAncestorPath(List<T> ancestors) {
136170
var ancestorPath = new ArrayList<Integer>();
137171
for (var i = 0; i < ancestors.size(); i++) {

vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/treegrid/ScrollToIndexTest.java

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,135 @@
1515
*/
1616
package com.vaadin.flow.component.treegrid;
1717

18+
import java.util.stream.IntStream;
19+
20+
import org.junit.After;
1821
import org.junit.Assert;
22+
import org.junit.Before;
1923
import org.junit.Test;
24+
import org.mockito.Mockito;
2025

26+
import com.vaadin.flow.component.UI;
2127
import com.vaadin.flow.data.provider.hierarchy.HierarchicalDataProvider.HierarchyFormat;
2228
import com.vaadin.flow.data.provider.hierarchy.TreeData;
2329
import com.vaadin.flow.data.provider.hierarchy.TreeDataProvider;
30+
import com.vaadin.flow.function.DeploymentConfiguration;
31+
import com.vaadin.flow.server.VaadinContext;
32+
import com.vaadin.flow.server.VaadinService;
33+
import com.vaadin.flow.server.VaadinSession;
2434

2535
public class ScrollToIndexTest {
26-
private TreeGrid<String> treeGrid = new TreeGrid<>();
27-
private TreeData<String> treeData = new TreeData<>();
36+
private TreeGrid<String> treeGrid;
37+
private TreeData<String> treeData;
38+
private UI ui;
39+
40+
@Before
41+
public void init() {
42+
ui = new UI();
43+
UI.setCurrent(ui);
44+
var mockSession = Mockito.mock(VaadinSession.class);
45+
var mockService = Mockito.mock(VaadinService.class);
46+
var mockContext = Mockito.mock(VaadinContext.class);
47+
var mockConfiguration = Mockito.mock(DeploymentConfiguration.class);
48+
Mockito.when(mockSession.getService()).thenReturn(mockService);
49+
Mockito.when(mockSession.getConfiguration())
50+
.thenReturn(mockConfiguration);
51+
Mockito.when(mockService.getContext()).thenReturn(mockContext);
52+
ui.getInternals().setSession(mockSession);
53+
treeGrid = new TreeGrid<>();
54+
ui.add(treeGrid);
55+
treeData = getTreeData();
56+
}
57+
58+
@After
59+
public void tearDown() {
60+
UI.setCurrent(null);
61+
}
2862

2963
@Test
30-
public void nestedHierarchyFormat_scrollToIndexPath_doesNotThrow() {
64+
public void flattenedHierarchyFormat_scrollToIndexPath_throwsUnsupportedOperationException() {
65+
treeGrid.setDataProvider(
66+
new TreeDataProvider<>(treeData, HierarchyFormat.FLATTENED));
67+
Assert.assertThrows(UnsupportedOperationException.class,
68+
() -> treeGrid.scrollToIndex(0, 0));
69+
}
70+
71+
@Test
72+
public void nestedHierarchyFormat_scrollToIndexPathWithCollapsedParent_expandsParent() {
3173
treeGrid.setDataProvider(
3274
new TreeDataProvider<>(treeData, HierarchyFormat.NESTED));
33-
treeGrid.scrollToIndex(0, 0);
75+
var rootItem = treeData.getRootItems().get(10);
76+
var firstChild = treeData.getChildren(rootItem).getFirst();
77+
Assert.assertFalse(treeGrid.isExpanded(rootItem));
78+
Assert.assertFalse(treeGrid.isExpanded(firstChild));
79+
treeGrid.scrollToIndex(10, 0);
80+
Assert.assertTrue(treeGrid.isExpanded(rootItem));
81+
Assert.assertFalse(treeGrid.isExpanded(firstChild));
3482
}
3583

3684
@Test
37-
public void flattenedHierarchyFormat_scrollToIndexPath_throws() {
85+
public void flattenedHierarchyFormat_scrollToIndexNotPresent_notScrolled() {
3886
treeGrid.setDataProvider(
3987
new TreeDataProvider<>(treeData, HierarchyFormat.FLATTENED));
40-
Assert.assertThrows(UnsupportedOperationException.class,
41-
() -> treeGrid.scrollToIndex(0, 0));
88+
assertScrollToIndexCalled(false, 1000);
89+
assertScrollToIndexCalled(false, -1000);
90+
}
91+
92+
@Test
93+
public void flattenedHierarchyFormat_scrollToIndex_scrolled() {
94+
treeGrid.setDataProvider(
95+
new TreeDataProvider<>(treeData, HierarchyFormat.FLATTENED));
96+
assertScrollToIndexCalled(true, 10);
97+
assertScrollToIndexCalled(true, -10);
98+
}
99+
100+
@Test
101+
public void nestedHierarchyFormat_scrollToPathNotPresent_notScrolled() {
102+
treeGrid.setDataProvider(
103+
new TreeDataProvider<>(treeData, HierarchyFormat.NESTED));
104+
assertScrollToPathCalled(false, 1000);
105+
assertScrollToPathCalled(false, -1000);
106+
assertScrollToPathCalled(false, 10, 5, 5);
107+
assertScrollToPathCalled(false, -10, -5, -5);
108+
}
109+
110+
@Test
111+
public void nestedHierarchyFormat_scrollToPath_scrolled() {
112+
treeGrid.setDataProvider(
113+
new TreeDataProvider<>(treeData, HierarchyFormat.NESTED));
114+
assertScrollToPathCalled(true, 10, 5);
115+
assertScrollToPathCalled(true, -10, -5);
116+
}
117+
118+
private void assertScrollToIndexCalled(boolean called, int index) {
119+
treeGrid.scrollToIndex(index);
120+
Assert.assertEquals(called, isScrollToIndexCalled());
121+
}
122+
123+
private void assertScrollToPathCalled(boolean called, int... path) {
124+
treeGrid.scrollToIndex(path);
125+
Assert.assertEquals(called, isScrollToIndexCalled());
126+
}
127+
128+
private boolean isScrollToIndexCalled() {
129+
ui.getInternals().getStateTree().runExecutionsBeforeClientResponse();
130+
ui.getInternals().getStateTree().collectChanges(ignore -> {
131+
});
132+
var invocations = ui.getInternals().dumpPendingJavaScriptInvocations();
133+
return invocations.stream().anyMatch(invocation -> invocation
134+
.getInvocation().getExpression().contains("scrollToIndex"));
135+
}
136+
137+
private static TreeData<String> getTreeData() {
138+
var rootItemCount = 50;
139+
var childItemCountPerRootItem = 10;
140+
var treeData = new TreeData<String>();
141+
var rootItems = IntStream.range(0, rootItemCount)
142+
.mapToObj(i -> "Item " + i).toList();
143+
treeData.addRootItems(rootItems);
144+
rootItems.forEach(rootItem -> treeData.addItems(rootItem,
145+
IntStream.range(0, childItemCountPerRootItem)
146+
.mapToObj(i -> rootItem + "-" + i)));
147+
return treeData;
42148
}
43149
}

0 commit comments

Comments
 (0)