summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Wagner <wbadam@users.noreply.github.com>2018-02-08 11:20:32 +0200
committerTeemu Suo-Anttila <tsuoanttila@users.noreply.github.com>2018-02-08 11:20:32 +0200
commit1817a82ed8f3c8839c18d9ce6b68e5838940a22c (patch)
tree3a110ba043aa4bea415b4060c4eba4439703711b
parent299520fac11dac9b31d6fd762a3eb76ef827ec5b (diff)
downloadvaadin-framework-1817a82ed8f3c8839c18d9ce6b68e5838940a22c.tar.gz
vaadin-framework-1817a82ed8f3c8839c18d9ce6b68e5838940a22c.zip
Add recursive expand and collapse method to TreeGrid and Tree (#10283)
-rw-r--r--server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java157
-rw-r--r--server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java79
-rw-r--r--server/src/main/java/com/vaadin/ui/Tree.java38
-rw-r--r--server/src/main/java/com/vaadin/ui/TreeGrid.java120
-rw-r--r--server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java4
-rw-r--r--server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithNumerousDataTest.java2
-rw-r--r--shared/src/main/java/com/vaadin/shared/Range.java7
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/LazyHierarchicalDataProvider.java3
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java10
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursively.java106
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java19
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursivelyTest.java105
12 files changed, 599 insertions, 51 deletions
diff --git a/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java b/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
index ffdcf042c9..e09681978d 100644
--- a/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
+++ b/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
@@ -156,33 +156,83 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
}
/**
- * Collapses given item, removing all its subtrees. Calling this method will
- * have no effect if the row is already collapsed.
+ * Collapses the given item and removes its sub-hierarchy. Calling this
+ * method will have no effect if the row is already collapsed.
*
* @param item
* the item to collapse
*/
public void collapse(T item) {
- if (mapper.isExpanded(item)) {
- doCollapse(item, mapper.getIndexOf(item));
- }
+ collapse(item, true);
+ }
+
+ /**
+ * Collapses the given item and removes its sub-hierarchy. Calling this
+ * method will have no effect if the row is already collapsed.
+ * {@code syncAndRefresh} indicates whether the changes should be
+ * synchronised to the client and the data provider be notified.
+ *
+ * @param item
+ * the item to collapse
+ * @param syncAndRefresh
+ * {@code true} if the changes should be synchronised to the
+ * client and the data provider should be notified of the
+ * changes, {@code false} otherwise.
+ */
+ public void collapse(T item, boolean syncAndRefresh) {
+ Integer index = syncAndRefresh ? mapper.getIndexOf(item).orElse(null) : null;
+ doCollapse(item, index, syncAndRefresh);
}
/**
- * Collapses given item, removing all its subtrees. Calling this method will
- * have no effect if the row is already collapsed. The index is provided by
- * the client-side or calculated from a full data request.
+ * Collapses the given item and removes its sub-hierarchy. Calling this
+ * method will have no effect if the row is already collapsed.
*
- * @see #collapse(Object)
+ * @param item
+ * the item to collapse
+ * @param index
+ * the index of the item
+ */
+ public void collapse(T item, Integer index) {
+ doCollapse(item, index, true);
+ }
+
+ /**
+ * Collapses given item and removes its sub-hierarchy. Calling this method
+ * will have no effect if the row is already collapsed. The index is
+ * provided by the client-side or calculated from a full data request.
*
+ *
* @param item
* the item to collapse
* @param index
* the index of the item
+ * @deprecated Use {@link #collapse(Object, Integer)} instead.
*/
+ @Deprecated
public void doCollapse(T item, Optional<Integer> index) {
- if (mapper.isExpanded(item)) {
- Range removedRows = mapper.doCollapse(item, index);
+ doCollapse(item, index.orElse(null), true);
+ }
+
+ /**
+ * Collapses the given item and removes its sub-hierarchy. Calling this
+ * method will have no effect if the row is already collapsed. The index is
+ * provided by the client-side or calculated from a full data request.
+ * {@code syncAndRefresh} indicates whether the changes should be
+ * synchronised to the client and the data provider be notified.
+ *
+ * @param item
+ * the item to collapse
+ * @param index
+ * the index of the item
+ * @param syncAndRefresh
+ * {@code true} if the changes should be synchronised to the
+ * client and the data provider should be notified of the
+ * changes, {@code false} otherwise.
+ */
+ private void doCollapse(T item, Integer index, boolean syncAndRefresh) {
+ Range removedRows = mapper.collapse(item, index);
+ if (syncAndRefresh) {
if (!reset && !removedRows.isEmpty()) {
getClientRpc().removeRows(removedRows.getStart(),
removedRows.length());
@@ -193,44 +243,97 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
/**
* Expands the given item. Calling this method will have no effect if the
- * row is already expanded.
+ * item is already expanded or if it has no children.
*
* @param item
* the item to expand
*/
public void expand(T item) {
- if (!mapper.isExpanded(item) && mapper.hasChildren(item)) {
- doExpand(item, mapper.getIndexOf(item));
- }
+ expand(item, true);
}
/**
- * Expands the given item at given index. Calling this method will have no
- * effect if the row is already expanded. The index is provided by the
- * client-side or calculated from a full data request.
+ * Expands the given item. Calling this method will have no effect if the
+ * item is already expanded or if it has no children. {@code syncAndRefresh}
+ * indicates whether the changes should be synchronised to the client and
+ * the data provider be notified.
*
- * @see #expand(Object)
+ * @param item
+ * the item to expand
+ * @param syncAndRefresh
+ * {@code true} if the changes should be synchronised to the
+ * client and the data provider should be notified of the
+ * changes, {@code
+ * false} otherwise.
+ */
+ public void expand(T item, boolean syncAndRefresh) {
+ Integer index = syncAndRefresh ? mapper.getIndexOf(item).orElse(null) : null;
+ doExpand(item, index, syncAndRefresh);
+ }
+
+ /**
+ * Expands the given item at the given index. Calling this method will have
+ * no effect if the item is already expanded.
*
* @param item
* the item to expand
* @param index
* the index of the item
*/
- public void doExpand(T item, Optional<Integer> index) {
- if (!mapper.isExpanded(item)) {
- Range addedRows = mapper.doExpand(item, index);
+ public void expand(T item, Integer index) {
+ doExpand(item, index, true);
+ }
+
+ /**
+ * Expands the given item. Calling this method will have no effect if the
+ * item is already expanded or if it has no children. The index is provided
+ * by the client-side or calculated from a full data request.
+ * {@code syncAndRefresh} indicates whether the changes should be
+ * synchronised to the client and the data provider be notified.
+ *
+ * @param item
+ * the item to expand
+ * @param index
+ * the index of the item
+ * @param syncAndRefresh
+ * {@code true} if the changes should be synchronised to the
+ * client and the data provider should be notified of the
+ * changes, {@code false} otherwise.
+ */
+ private void doExpand(T item, Integer index, boolean syncAndRefresh) {
+ Range addedRows = mapper.expand(item, index);
+ if (syncAndRefresh) {
if (!reset && !addedRows.isEmpty()) {
- int start = addedRows.getStart();
- getClientRpc().insertRows(start, addedRows.length());
- Stream<T> children = mapper.fetchItems(item,
- Range.withLength(0, addedRows.length()));
- pushData(start, children.collect(Collectors.toList()));
+ getClientRpc()
+ .insertRows(addedRows.getStart(), addedRows.length());
+ Stream<T> children = mapper
+ .fetchItems(item,
+ Range.withLength(0, addedRows.length()));
+ pushData(addedRows.getStart(),
+ children.collect(Collectors.toList()));
}
refresh(item);
}
}
/**
+ * Expands the given item at given index. Calling this method will have no
+ * effect if the row is already expanded. The index is provided by the
+ * client-side or calculated from a full data request.
+ *
+ * @param item
+ * the item to expand
+ * @param index
+ * the index of the item
+ * @see #expand(Object)
+ * @deprecated use {@link #expand(Object, Integer)} instead
+ */
+ @Deprecated
+ public void doExpand(T item, Optional<Integer> index) {
+ expand(item, index.orElse(null));
+ }
+
+ /**
* Returns whether given item has children.
*
* @param item
diff --git a/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java b/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
index e918c4a209..f6f9f8b9a4 100644
--- a/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
+++ b/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
@@ -123,46 +123,89 @@ public class HierarchyMapper<T, F> implements DataGenerator<T> {
* @param item
* the item to expand
* @param position
+ * the index of the item
+ * @return range of rows added by expanding the item
+ */
+ public Range expand(T item, Integer position) {
+ if (doExpand(item) && position != null) {
+ return Range.withLength(position + 1,
+ (int) getHierarchy(item, false).count());
+ }
+
+ return Range.emptyRange();
+ }
+
+ /**
+ * Expands the given item.
+ *
+ * @param item
+ * the item to expand
+ * @param position
* the index of item
* @return range of rows added by expanding the item
+ * @deprecated Use {@link #expand(Object, Integer)} instead.
*/
+ @Deprecated
public Range doExpand(T item, Optional<Integer> position) {
- Range rows = Range.withLength(0, 0);
+ return expand(item, position.orElse(null));
+ }
+
+ /**
+ * Expands the given item if it is collapsed and has children, and returns
+ * whether this method expanded the item.
+ *
+ * @param item
+ * the item to expand
+ * @return {@code true} if this method expanded the item, {@code false}
+ * otherwise
+ */
+ private boolean doExpand(T item) {
+ boolean expanded = false;
if (!isExpanded(item) && hasChildren(item)) {
- Object id = getDataProvider().getId(item);
- expandedItemIds.add(id);
- if (position.isPresent()) {
- rows = Range.withLength(position.get() + 1,
- (int) getHierarchy(item, false).count());
- }
+ expandedItemIds.add(getDataProvider().getId(item));
+ expanded = true;
}
- return rows;
+ return expanded;
}
/**
* Collapses the given item.
*
* @param item
- * the item to expand
+ * the item to collapse
* @param position
- * the index of item
+ * the index of the item
*
* @return range of rows removed by collapsing the item
*/
- public Range doCollapse(T item, Optional<Integer> position) {
- Range removedRows = Range.withLength(0, 0);
+ public Range collapse(T item, Integer position) {
+ Range removedRows = Range.emptyRange();
if (isExpanded(item)) {
- Object id = getDataProvider().getId(item);
- if (position.isPresent()) {
- long childCount = getHierarchy(item, false).count();
- removedRows = Range.withLength(position.get() + 1,
- (int) childCount);
+ if (position != null) {
+ removedRows = Range.withLength(position + 1,
+ (int) getHierarchy(item, false).count());
}
- expandedItemIds.remove(id);
+ expandedItemIds.remove(getDataProvider().getId(item));
}
return removedRows;
}
+ /**
+ * Collapses the given item.
+ *
+ * @param item
+ * the item to collapse
+ * @param position
+ * the index of item
+ *
+ * @return range of rows removed by collapsing the item
+ * @deprecated Use {@link #collapse(Object, Integer)} instead.
+ */
+ @Deprecated
+ public Range doCollapse(T item, Optional<Integer> position) {
+ return collapse(item, position.orElse(null));
+ }
+
@Override
public void generateData(T item, JsonObject jsonObject) {
JsonObject hierarchyData = Json.createObject();
diff --git a/server/src/main/java/com/vaadin/ui/Tree.java b/server/src/main/java/com/vaadin/ui/Tree.java
index 4e96f4a3e4..bebe6972b5 100644
--- a/server/src/main/java/com/vaadin/ui/Tree.java
+++ b/server/src/main/java/com/vaadin/ui/Tree.java
@@ -442,6 +442,25 @@ public class Tree<T> extends Composite
}
/**
+ * Expands the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code expandRecursively(items, 0)} expands only
+ * the given items while {@code expandRecursively(items, 2)} expands the
+ * given items as well as their children and grandchildren.
+ *
+ * @param items
+ * the items to expand recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void expandRecursively(Collection<T> items, int depth) {
+ treeGrid.expandRecursively(items, depth);
+ }
+
+ /**
* Collapse the given items.
* <p>
* For items that are already collapsed, does nothing.
@@ -466,6 +485,25 @@ public class Tree<T> extends Composite
}
/**
+ * Collapse the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code collapseRecursively(items, 0)} collapses
+ * only the given items while {@code collapseRecursively(items, 2)}
+ * collapses the given items as well as their children and grandchildren.
+ *
+ * @param items
+ * the items to expand recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void collapseRecursively(Collection<T> items, int depth) {
+ treeGrid.collapseRecursively(items, depth);
+ }
+
+ /**
* Returns whether a given item is expanded or collapsed.
*
* @param item
diff --git a/server/src/main/java/com/vaadin/ui/TreeGrid.java b/server/src/main/java/com/vaadin/ui/TreeGrid.java
index 65329d24c8..1c5716cc61 100644
--- a/server/src/main/java/com/vaadin/ui/TreeGrid.java
+++ b/server/src/main/java/com/vaadin/ui/TreeGrid.java
@@ -175,12 +175,12 @@ public class TreeGrid<T> extends Grid<T>
userOriginated) -> {
T item = getDataCommunicator().getKeyMapper().get(rowKey);
if (collapse && getDataCommunicator().isExpanded(item)) {
- getDataCommunicator().doCollapse(item, Optional.of(rowIndex));
+ getDataCommunicator().collapse(item, rowIndex);
fireCollapseEvent(
getDataCommunicator().getKeyMapper().get(rowKey),
userOriginated);
} else if (!collapse && !getDataCommunicator().isExpanded(item)) {
- getDataCommunicator().doExpand(item, Optional.of(rowIndex));
+ getDataCommunicator().expand(item, rowIndex);
fireExpandEvent(
getDataCommunicator().getKeyMapper().get(rowKey),
userOriginated);
@@ -345,6 +345,64 @@ public class TreeGrid<T> extends Grid<T>
}
/**
+ * Expands the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code expandRecursively(items, 0)} expands only
+ * the given items while {@code expandRecursively(items, 2)} expands the
+ * given items as well as their children and grandchildren.
+ * <p>
+ * This method will <i>not</i> fire events for expanded nodes.
+ *
+ * @param items
+ * the items to expand recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void expandRecursively(Collection<T> items, int depth) {
+ expandRecursively(items.stream(), depth);
+ }
+
+ /**
+ * Expands the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code expandRecursively(items, 0)} expands only
+ * the given items while {@code expandRecursively(items, 2)} expands the
+ * given items as well as their children and grandchildren.
+ * <p>
+ * This method will <i>not</i> fire events for expanded nodes.
+ *
+ * @param items
+ * the items to expand recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void expandRecursively(Stream<T> items, int depth) {
+ if (depth < 0) {
+ return;
+ }
+
+ HierarchicalDataCommunicator<T> communicator = getDataCommunicator();
+ items.forEach(item -> {
+ if (communicator.hasChildren(item)) {
+ communicator.expand(item, false);
+
+ expandRecursively(
+ getDataProvider().fetchChildren(
+ new HierarchicalQuery<>(null, item)),
+ depth - 1);
+ }
+ });
+
+ getDataProvider().refreshAll();
+ }
+
+ /**
* Collapse the given items.
* <p>
* For items that are already collapsed, does nothing.
@@ -375,6 +433,64 @@ public class TreeGrid<T> extends Grid<T>
}
/**
+ * Collapse the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code collapseRecursively(items, 0)} collapses
+ * only the given items while {@code collapseRecursively(items, 2)}
+ * collapses the given items as well as their children and grandchildren.
+ * <p>
+ * This method will <i>not</i> fire events for collapsed nodes.
+ *
+ * @param items
+ * the items to collapse recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void collapseRecursively(Collection<T> items, int depth) {
+ collapseRecursively(items.stream(), depth);
+ }
+
+ /**
+ * Collapse the given items and their children recursively until the given
+ * depth.
+ * <p>
+ * {@code depth} describes the maximum distance between a given item and its
+ * descendant, meaning that {@code collapseRecursively(items, 0)} collapses
+ * only the given items while {@code collapseRecursively(items, 2)}
+ * collapses the given items as well as their children and grandchildren.
+ * <p>
+ * This method will <i>not</i> fire events for collapsed nodes.
+ *
+ * @param items
+ * the items to collapse recursively
+ * @param depth
+ * the maximum depth of recursion
+ * @since
+ */
+ public void collapseRecursively(Stream<T> items, int depth) {
+ if (depth < 0) {
+ return;
+ }
+
+ HierarchicalDataCommunicator<T> communicator = getDataCommunicator();
+ items.forEach(item -> {
+ if (communicator.hasChildren(item)) {
+ collapseRecursively(
+ getDataProvider().fetchChildren(
+ new HierarchicalQuery<>(null, item)),
+ depth - 1);
+
+ communicator.collapse(item, false);
+ }
+ });
+
+ getDataProvider().refreshAll();
+ }
+
+ /**
* Returns whether a given item is expanded or collapsed.
*
* @param item
diff --git a/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java b/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java
index 80d6a70cfd..81ee908a52 100644
--- a/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java
+++ b/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java
@@ -209,11 +209,11 @@ public class HierarchyMapperWithDataTest {
}
private void expand(Node node) {
- insertRows(mapper.doExpand(node, mapper.getIndexOf(node)));
+ insertRows(mapper.expand(node, mapper.getIndexOf(node).orElse(null)));
}
private void collapse(Node node) {
- removeRows(mapper.doCollapse(node, mapper.getIndexOf(node)));
+ removeRows(mapper.collapse(node, mapper.getIndexOf(node).orElse(null)));
}
private void verifyFetchIsCorrect(List<Node> expectedResult, Range range) {
diff --git a/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithNumerousDataTest.java b/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithNumerousDataTest.java
index 6c0091f8bf..58208a6cc3 100644
--- a/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithNumerousDataTest.java
+++ b/server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithNumerousDataTest.java
@@ -63,7 +63,7 @@ public class HierarchyMapperWithNumerousDataTest {
}
private void expand(Node node) {
- insertRows(mapper.doExpand(node, mapper.getIndexOf(node)));
+ insertRows(mapper.expand(node, mapper.getIndexOf(node).orElse(null)));
}
public void insertRows(Range range) {
diff --git a/shared/src/main/java/com/vaadin/shared/Range.java b/shared/src/main/java/com/vaadin/shared/Range.java
index 042cc527a0..b47963fd50 100644
--- a/shared/src/main/java/com/vaadin/shared/Range.java
+++ b/shared/src/main/java/com/vaadin/shared/Range.java
@@ -31,6 +31,9 @@ import java.io.Serializable;
* @author Vaadin Ltd
*/
public final class Range implements Serializable {
+
+ private static final Range EMPTY = Range.withLength(0, 0);
+
private final int start;
private final int end;
@@ -90,6 +93,10 @@ public final class Range implements Serializable {
return new Range(start, start + length);
}
+ public static Range emptyRange() {
+ return EMPTY;
+ }
+
/**
* Creates a new range between two numbers: <code>[start..end[</code>.
*
diff --git a/uitest/src/main/java/com/vaadin/tests/components/treegrid/LazyHierarchicalDataProvider.java b/uitest/src/main/java/com/vaadin/tests/components/treegrid/LazyHierarchicalDataProvider.java
index a468f34d29..014ee7d1f0 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/treegrid/LazyHierarchicalDataProvider.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/treegrid/LazyHierarchicalDataProvider.java
@@ -49,7 +49,8 @@ public class LazyHierarchicalDataProvider extends
.flatMap(parent -> Optional.of(parent.getId()));
List<HierarchicalTestBean> list = new ArrayList<>();
- for (int i = 0; i < query.getLimit(); i++) {
+ int limit = Math.min(query.getLimit(), nodesPerLevel);
+ for (int i = 0; i < limit; i++) {
list.add(new HierarchicalTestBean(parentKey.orElse(null), depth,
i + query.getOffset()));
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java
index 2ac0c0673d..c12baf3900 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java
@@ -180,6 +180,11 @@ public class TreeGridBasicFeatures extends AbstractComponentTest<TreeGrid> {
createClickAction("Expand 2 | 1", "Server-side expand",
(treeGrid, value, data) -> treeGrid.expand(value),
new HierarchicalTestBean("/0/0/1/1", 2, 1));
+
+ createClickAction("Expand 0 | 0 recursively", "Server-side expand",
+ (treeGrid, value, data) -> treeGrid
+ .expandRecursively(Arrays.asList(value), 1),
+ new HierarchicalTestBean(null, 0, 0));
}
@SuppressWarnings("unchecked")
@@ -194,6 +199,11 @@ public class TreeGridBasicFeatures extends AbstractComponentTest<TreeGrid> {
createClickAction("Collapse 2 | 1", "Server-side collapse",
(treeGrid, value, data) -> treeGrid.collapse(value),
new HierarchicalTestBean("/0/0/1/1", 2, 1));
+
+ createClickAction("Collapse 0 | 0 recursively", "Server-side collapse",
+ (treeGrid, value, data) -> treeGrid
+ .collapseRecursively(Arrays.asList(value), 2),
+ new HierarchicalTestBean(null, 0, 0));
}
@SuppressWarnings("unchecked")
diff --git a/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursively.java b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursively.java
new file mode 100644
index 0000000000..f85eba5815
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursively.java
@@ -0,0 +1,106 @@
+package com.vaadin.tests.components.treegrid;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.RadioButtonGroup;
+import com.vaadin.ui.TreeGrid;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class TreeGridExpandCollapseRecursively extends AbstractTestUI {
+
+ private static class Directory {
+
+ private String name;
+ private Directory parent;
+ private List<Directory> subDirectories = new ArrayList<>();
+
+ public Directory(String name, Directory parent) {
+ this.name = name;
+ this.parent = parent;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Directory getParent() {
+ return parent;
+ }
+
+ public void setParent(
+ Directory parent) {
+ this.parent = parent;
+ }
+
+ public List<Directory> getSubDirectories() {
+ return subDirectories;
+ }
+
+ public void setSubDirectories(List<Directory> subDirectories) {
+ this.subDirectories = subDirectories;
+ }
+ }
+
+ private static final int DEPTH = 4;
+ private static final int CHILDREN = 5;
+
+ @Override
+ protected void setup(VaadinRequest request) {
+
+ Collection<Directory> roots = generateDirectoryStructure(DEPTH);
+
+ TreeGrid<Directory> grid = new TreeGrid<>();
+ grid.addColumn(item -> "Item" + item.getName());
+
+ grid.setItems(roots, Directory::getSubDirectories);
+
+ RadioButtonGroup<Integer> depthSelector = new RadioButtonGroup<>(
+ "Depth", Arrays.asList(0, 1, 2, 3));
+ depthSelector.addStyleName("horizontal");
+ depthSelector.setValue(3);
+
+ HorizontalLayout buttons = new HorizontalLayout();
+ buttons.addComponent(new Button("Expand recursively", e -> grid
+ .expandRecursively(roots, depthSelector.getValue())));
+ buttons.addComponent(new Button("Collapse recursively", e -> grid
+ .collapseRecursively(roots, depthSelector.getValue())));
+
+ addComponents(depthSelector, buttons, grid);
+ }
+
+ private Collection<Directory> generateDirectoryStructure(int depth) {
+ return generateDirectories(depth, null, CHILDREN);
+ }
+
+ private Collection<Directory> generateDirectories(int depth,
+ Directory parent, int childCount) {
+ Collection<Directory> dirs = new ArrayList<>();
+ if (depth >= 0) {
+ for (int i = 0; i < childCount; i++) {
+ String name = parent != null
+ ? parent.getName() + "-" + i
+ : "-" + i;
+ Directory dir = new Directory(name, parent);
+ if (parent != null) {
+ parent.getSubDirectories().add(dir);
+ }
+ dirs.add(dir);
+
+ generateDirectories(depth - 1, dir, childCount);
+ }
+ }
+ return dirs;
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
index 43c08415df..a96e2d3c1d 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
@@ -95,6 +95,25 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+ // expand 0 | 0 recursively
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0 recursively");
+ assertEquals(15, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "1 | 0", "2 | 0" });
+
+ // collapse 0 | 0 recursively
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0 recursively");
+ assertEquals(3, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+
+ // expanding 0 | 0 should result in 3 additional nodes after recursive
+ // collapse
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
+
assertNoSystemNotifications();
assertNoErrorNotifications();
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursivelyTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursivelyTest.java
new file mode 100644
index 0000000000..6e2451b96a
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridExpandCollapseRecursivelyTest.java
@@ -0,0 +1,105 @@
+package com.vaadin.tests.components.treegrid;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.RadioButtonGroupElement;
+import com.vaadin.testbench.elements.TreeGridElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+import static org.junit.Assert.assertEquals;
+
+public class TreeGridExpandCollapseRecursivelyTest extends SingleBrowserTest {
+
+ private static final int rowCount0 = 5;
+ private static final int rowCount1 = rowCount0 + rowCount0 * 5;
+ private static final int rowCount2 =
+ rowCount1 + (rowCount1 - rowCount0) * 5;
+ private static final int rowCount3 =
+ rowCount2 + (rowCount2 - rowCount1) * 5;
+ private static final int rowCount4 =
+ rowCount3 + (rowCount3 - rowCount2) * 5;
+
+ private TreeGridElement grid;
+ private RadioButtonGroupElement depthSelector;
+ private ButtonElement expandButton;
+ private ButtonElement collapseButton;
+
+ @Before
+ public void before() {
+ openTestURL();
+ grid = $(TreeGridElement.class).first();
+ depthSelector = $(RadioButtonGroupElement.class).first();
+ expandButton = $(ButtonElement.class).get(0);
+ collapseButton = $(ButtonElement.class).get(1);
+ }
+
+ @Test
+ public void expandVariousDepth() {
+ assertEquals(rowCount0, grid.getRowCount());
+
+ selectDepth(0);
+ expandButton.click();
+
+ assertEquals(rowCount1, grid.getRowCount());
+
+ selectDepth(1);
+ expandButton.click();
+
+ assertEquals(rowCount2, grid.getRowCount());
+
+ selectDepth(2);
+ expandButton.click();
+
+ assertEquals(rowCount3, grid.getRowCount());
+
+ selectDepth(3);
+ expandButton.click();
+
+ assertEquals(rowCount4, grid.getRowCount());
+ }
+
+ @Test(timeout = 5000)
+ public void expandAndCollapseAllItems() {
+ assertEquals(rowCount0, grid.getRowCount());
+
+ selectDepth(3);
+ expandButton.click();
+
+ assertEquals(rowCount4, grid.getRowCount());
+
+ collapseButton.click();
+
+ assertEquals(rowCount0, grid.getRowCount());
+ }
+
+ @Test
+ public void partialCollapse() {
+ assertEquals(rowCount0, grid.getRowCount());
+
+ selectDepth(3);
+ expandButton.click();
+
+ assertEquals(rowCount4, grid.getRowCount());
+
+ selectDepth(1);
+ collapseButton.click();
+
+ assertEquals(rowCount0, grid.getRowCount());
+
+ selectDepth(0);
+ expandButton.click();
+
+ assertEquals(rowCount1, grid.getRowCount());
+
+ // Open just one subtree to see if it is still fully expanded
+ grid.getExpandElement(2, 0).click();
+
+ assertEquals(rowCount1 + rowCount2, grid.getRowCount());
+ }
+
+ private void selectDepth(int depth) {
+ depthSelector.setValue(String.valueOf(depth));
+ }
+}