summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksi Hietanen <aleksi@vaadin.com>2017-04-06 13:09:31 +0300
committerIlia Motornyi <elmot@vaadin.com>2017-04-06 12:09:31 +0200
commit6ad53c7d6679cc60961d07ed0766b679795ac8a8 (patch)
tree0c5ff17c7662bb6c858571553f7e902e8ae3b432
parent6eed666314e1f9987bc6f52b4f2901ff7c0018b3 (diff)
downloadvaadin-framework-6ad53c7d6679cc60961d07ed0766b679795ac8a8.tar.gz
vaadin-framework-6ad53c7d6679cc60961d07ed0766b679795ac8a8.zip
Add server-side expand and collapse to TreeGrid (#9021)
* Add server-side expand and collapse to TreeGrid * Add javadocs * Fix variable naming in TreeGridHugeTreeTest * Fix review comments * Merge remote-tracking branch 'github/master' into 8759-server-expand * Clear pending expands when all data is dropped * Add documentation
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java81
-rw-r--r--documentation/components/components-treegrid.asciidoc22
-rw-r--r--server/src/main/java/com/vaadin/data/provider/DataCommunicator.java5
-rw-r--r--server/src/main/java/com/vaadin/data/provider/DataKeyMapper.java10
-rw-r--r--server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java78
-rw-r--r--server/src/main/java/com/vaadin/server/KeyMapper.java5
-rw-r--r--server/src/main/java/com/vaadin/ui/TreeGrid.java98
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/treegrid/NodeCollapseRpc.java6
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/treegrid/TreeGridClientRpc.java45
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeatures.java38
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTree.java57
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java126
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeTest.java60
13 files changed, 588 insertions, 43 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java b/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
index d8e91ceaa0..10935264c7 100644
--- a/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
+++ b/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
@@ -16,7 +16,10 @@
package com.vaadin.client.connectors.treegrid;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
import java.util.logging.Logger;
+import java.util.stream.IntStream;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.BrowserEvents;
@@ -25,6 +28,9 @@ import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Event;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.connectors.grid.GridConnector;
+import com.vaadin.client.data.AbstractRemoteDataSource;
+import com.vaadin.client.data.DataChangeHandler;
+import com.vaadin.client.data.DataSource;
import com.vaadin.client.renderers.HierarchyRenderer;
import com.vaadin.client.widget.grid.EventCellReference;
import com.vaadin.client.widget.grid.GridEventHandler;
@@ -32,10 +38,13 @@ import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.treegrid.TreeGrid;
import com.vaadin.client.widget.treegrid.events.TreeGridClickEvent;
import com.vaadin.client.widgets.Grid;
+import com.vaadin.shared.Range;
+import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.treegrid.FocusParentRpc;
import com.vaadin.shared.ui.treegrid.FocusRpc;
import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
+import com.vaadin.shared.ui.treegrid.TreeGridClientRpc;
import com.vaadin.shared.ui.treegrid.TreeGridCommunicationConstants;
import com.vaadin.shared.ui.treegrid.TreeGridState;
@@ -60,6 +69,8 @@ public class TreeGridConnector extends GridConnector {
private HierarchyRenderer hierarchyRenderer;
+ private Set<String> rowKeysPendingExpand = new HashSet<>();
+
@Override
public TreeGrid getWidget() {
return (TreeGrid) super.getWidget();
@@ -145,6 +156,54 @@ public class TreeGridConnector extends GridConnector {
// widget check
replaceClickEvent(getWidget(),
new TreeGridClickEvent(getWidget(), getEventCell(getWidget())));
+
+ registerRpc(TreeGridClientRpc.class, new TreeGridClientRpc() {
+
+ @Override
+ public void setExpanded(String key) {
+ rowKeysPendingExpand.add(key);
+ Range cache = ((AbstractRemoteDataSource) getDataSource())
+ .getCachedRange();
+ checkExpand(cache.getStart(), cache.length());
+ }
+
+ @Override
+ public void setCollapsed(String key) {
+ rowKeysPendingExpand.remove(key);
+ }
+ });
+ }
+
+ @Override
+ public void setDataSource(DataSource<JsonObject> dataSource) {
+ super.setDataSource(dataSource);
+ dataSource.addDataChangeHandler(new DataChangeHandler() {
+
+ @Override
+ public void dataUpdated(int firstRowIndex, int numberOfRows) {
+ checkExpand(firstRowIndex, numberOfRows);
+ }
+
+ @Override
+ public void dataRemoved(int firstRowIndex, int numberOfRows) {
+ // NO-OP
+ }
+
+ @Override
+ public void dataAdded(int firstRowIndex, int numberOfRows) {
+ // NO-OP
+ }
+
+ @Override
+ public void dataAvailable(int firstRowIndex, int numberOfRows) {
+ // NO-OP
+ }
+
+ @Override
+ public void resetDataAndSize(int estimatedNewDataSize) {
+ rowKeysPendingExpand.clear();
+ }
+ });
}
private native void replaceCellFocusEventHandler(Grid<?> grid,
@@ -173,7 +232,13 @@ public class TreeGridConnector extends GridConnector {
private void setCollapsed(int rowIndex, boolean collapsed) {
String rowKey = getRowKey(getDataSource().getRow(rowIndex));
getRpcProxy(NodeCollapseRpc.class).setNodeCollapsed(rowKey, rowIndex,
- collapsed);
+ collapsed, true);
+ }
+
+ private void setCollapsedServerInitiated(int rowIndex, boolean collapsed) {
+ String rowKey = getRowKey(getDataSource().getRow(rowIndex));
+ getRpcProxy(NodeCollapseRpc.class).setNodeCollapsed(rowKey, rowIndex,
+ collapsed, false);
}
/**
@@ -279,6 +344,20 @@ public class TreeGridConnector extends GridConnector {
}
}
+ private void checkExpand(int firstRowIndex, int numberOfRows) {
+ if (rowKeysPendingExpand.isEmpty()) {
+ return;
+ }
+ IntStream.range(firstRowIndex, firstRowIndex + numberOfRows)
+ .forEach(rowIndex -> {
+ String rowKey = getDataSource().getRow(rowIndex)
+ .getString(DataCommunicatorConstants.KEY);
+ if (rowKeysPendingExpand.remove(rowKey)) {
+ setCollapsedServerInitiated(rowIndex, false);
+ }
+ });
+ }
+
private static boolean isCollapsed(JsonObject rowData) {
assert rowData
.hasKey(TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION) : "missing hierarchy data for row "
diff --git a/documentation/components/components-treegrid.asciidoc b/documentation/components/components-treegrid.asciidoc
index 98d3d17839..77f82d1c15 100644
--- a/documentation/components/components-treegrid.asciidoc
+++ b/documentation/components/components-treegrid.asciidoc
@@ -53,7 +53,7 @@ treeGrid.setDataProvider(new InMemoryHierarchicalDataProvider<>(data));
// the first column gets the hierarchy indicator by default
treeGrid.addColumn(Project::getName).setCaption("Project Name");
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
-treeGrid.addColumn(Project::getdLastModified).setCaption("Last Modified");
+treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified");
----
The [classname]#HierarchyData# class can be used to build the hierarchical data structure,
@@ -78,6 +78,26 @@ dataProvider.refreshAll();
Note that for adding or removing nodes, you always need to call the [methodname]#refreshAll# method in the data provider you are using. The [methodname]#refreshItem# method can only be used when just the data for that item is updated, but not for updates that add or remove items.
+[[components.treegrid.expandcollapse]]
+== Expanding and Collapsing Nodes
+
+[classname]#TreeGrid# nodes that have children can be expanded and collapsed by either user interaction or through the server-side API:
+
+[source, java]
+----
+// Expands a child project. If the child project is not yet
+// in the visible hierarchy, nothing will be shown.
+treeGrid.expand(childProject);
+// Expands the root project. If child project now becomes
+// visible it is also expanded into view.
+treeGrid.expand(rootProject);
+// Collapses the child project.
+treeGrid.collapse(childProject);
+----
+
+To use the server-side API with a backend data provider the [methodname]#hashCode# method for the node's type must be implemented so that when the desired node is retrieved from the backend it has the same hash as the object passed to either [methodname]#expand# or [methodname]#collapse#.
+
+[[components.treegrid.hierarchycolumn]]
== Changing the Hierarchy Column
By default, the [classname]#TreeGrid# shows the hierarchy indicator by default in the first column of the grid.
diff --git a/server/src/main/java/com/vaadin/data/provider/DataCommunicator.java b/server/src/main/java/com/vaadin/data/provider/DataCommunicator.java
index ae3c6f4cc1..ffdd79f36e 100644
--- a/server/src/main/java/com/vaadin/data/provider/DataCommunicator.java
+++ b/server/src/main/java/com/vaadin/data/provider/DataCommunicator.java
@@ -463,7 +463,10 @@ public class DataCommunicator<T> extends AbstractExtension {
}
}
- private void dropAllData() {
+ /**
+ * Drops all data associated with this data communicator.
+ */
+ protected void dropAllData() {
for (DataGenerator<T> g : generators) {
g.destroyAllData();
}
diff --git a/server/src/main/java/com/vaadin/data/provider/DataKeyMapper.java b/server/src/main/java/com/vaadin/data/provider/DataKeyMapper.java
index 0ec6748a85..f5be454f86 100644
--- a/server/src/main/java/com/vaadin/data/provider/DataKeyMapper.java
+++ b/server/src/main/java/com/vaadin/data/provider/DataKeyMapper.java
@@ -39,6 +39,16 @@ public interface DataKeyMapper<T> extends Serializable {
String key(T dataObject);
/**
+ * Check whether this key mapper contains the given data object.
+ *
+ * @param dataObject
+ * the data object to check
+ * @return {@code true} if the given data object is contained in this key
+ * mapper, {@code false} otherwise
+ */
+ boolean has(T dataObject);
+
+ /**
* Gets the data object identified by given key.
*
* @param key
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 b986f74ea3..fedad9dd55 100644
--- a/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
+++ b/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
@@ -17,8 +17,11 @@ package com.vaadin.data.provider;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.logging.Logger;
@@ -27,6 +30,7 @@ import java.util.stream.Stream;
import com.vaadin.data.HierarchyData;
import com.vaadin.data.provider.HierarchyMapper.TreeLevelQuery;
+import com.vaadin.data.provider.HierarchyMapper.TreeNode;
import com.vaadin.server.SerializableConsumer;
import com.vaadin.server.SerializablePredicate;
import com.vaadin.shared.Range;
@@ -58,6 +62,8 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
private HierarchyMapper mapper = new HierarchyMapper();
+ private Set<String> rowKeysPendingExpand = new HashSet<>();
+
/**
* Collapse allowed provider used to allow/disallow collapsing nodes.
*/
@@ -278,13 +284,19 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
// cannot drop expanded rows since the parent item is needed always
// when fetching more rows
String itemKey = keys.getString(i);
- if (mapper.isCollapsed(itemKey)) {
+ if (mapper.isCollapsed(itemKey) && !rowKeysPendingExpand.contains(itemKey)) {
getActiveDataHandler().dropActiveData(itemKey);
}
}
}
@Override
+ protected void dropAllData() {
+ super.dropAllData();
+ rowKeysPendingExpand.clear();
+ }
+
+ @Override
public HierarchicalDataProvider<T, ?> getDataProvider() {
return (HierarchicalDataProvider<T, ?>) super.getDataProvider();
}
@@ -400,6 +412,8 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
return false;
}
mapper.expand(expandedRowKey, expandedRowIndex, expandedNodeSize);
+ rowKeysPendingExpand.remove(expandedRowKey);
+
getClientRpc().insertRows(expandedRowIndex + 1, expandedNodeSize);
// TODO optimize by sending "just enough" of the expanded items
// directly
@@ -414,6 +428,68 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
}
/**
+ * Set an item as pending expansion.
+ * <p>
+ * Calling this method reserves a communication key for the item that is
+ * guaranteed to not be invalidated until the item is expanded. Has no
+ * effect and returns an empty optional if the given item is already
+ * expanded or has no children.
+ *
+ * @param item
+ * the item to set as pending expansion
+ * @return an optional of the communication key used for the item, empty if
+ * the item cannot be expanded
+ */
+ public Optional<String> setPendingExpand(T item) {
+ Objects.requireNonNull(item, "Item cannot be null");
+ if (getKeyMapper().has(item)
+ && !mapper.isCollapsed(getKeyMapper().key(item))) {
+ // item is already expanded
+ return Optional.empty();
+ }
+ if (!getDataProvider().hasChildren(item)) {
+ // ignore item with no children
+ return Optional.empty();
+ }
+ String key = getKeyMapper().key(item);
+ rowKeysPendingExpand.add(key);
+ return Optional.of(key);
+ }
+
+ /**
+ * Collapse an item.
+ * <p>
+ * This method will either collapse an item directly, or remove its pending
+ * expand status. If the item is not expanded or pending expansion, calling
+ * this method has no effect.
+ *
+ * @param item
+ * the item to collapse
+ * @return an optional of the communication key used for the item, empty if
+ * the item cannot be collapsed
+ */
+ public Optional<String> collapseItem(T item) {
+ Objects.requireNonNull(item, "Item cannot be null");
+ if (!getKeyMapper().has(item)) {
+ // keymapper should always have items that are expanded or pending
+ // expand
+ return Optional.empty();
+ }
+ String nodeKey = getKeyMapper().key(item);
+ Optional<TreeNode> node = mapper.getNodeForKey(nodeKey);
+ if (node.isPresent()) {
+ rowKeysPendingExpand.remove(nodeKey);
+ doCollapse(nodeKey, node.get().getStartIndex() - 1);
+ return Optional.of(nodeKey);
+ }
+ if (rowKeysPendingExpand.contains(nodeKey)) {
+ rowKeysPendingExpand.remove(nodeKey);
+ return Optional.of(nodeKey);
+ }
+ return Optional.empty();
+ }
+
+ /**
* Sets the item collapse allowed provider for this
* HierarchicalDataCommunicator. The provider should return {@code true} for
* any item that the user can collapse.
diff --git a/server/src/main/java/com/vaadin/server/KeyMapper.java b/server/src/main/java/com/vaadin/server/KeyMapper.java
index 7ed723be66..58058c6f69 100644
--- a/server/src/main/java/com/vaadin/server/KeyMapper.java
+++ b/server/src/main/java/com/vaadin/server/KeyMapper.java
@@ -64,6 +64,11 @@ public class KeyMapper<V> implements DataKeyMapper<V>, Serializable {
return key;
}
+ @Override
+ public boolean has(V o) {
+ return objectKeyMap.containsKey(o);
+ }
+
/**
* Retrieves object with the key.
*
diff --git a/server/src/main/java/com/vaadin/ui/TreeGrid.java b/server/src/main/java/com/vaadin/ui/TreeGrid.java
index d91a0f9af1..b5da3f8645 100644
--- a/server/src/main/java/com/vaadin/ui/TreeGrid.java
+++ b/server/src/main/java/com/vaadin/ui/TreeGrid.java
@@ -40,6 +40,7 @@ import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.treegrid.FocusParentRpc;
import com.vaadin.shared.ui.treegrid.FocusRpc;
import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
+import com.vaadin.shared.ui.treegrid.TreeGridClientRpc;
import com.vaadin.shared.ui.treegrid.TreeGridState;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
@@ -117,6 +118,8 @@ public class TreeGrid<T> extends Grid<T> {
private final T expandedItem;
+ private final boolean userOriginated;
+
/**
* Construct an expand event.
*
@@ -124,10 +127,15 @@ public class TreeGrid<T> extends Grid<T> {
* the tree grid this event originated from
* @param expandedItem
* the item that was expanded
+ * @param userOriginated
+ * whether the expand was triggered by a user interaction or
+ * the server
*/
- public ExpandEvent(TreeGrid<T> source, T expandedItem) {
+ public ExpandEvent(TreeGrid<T> source, T expandedItem,
+ boolean userOriginated) {
super(source);
this.expandedItem = expandedItem;
+ this.userOriginated = userOriginated;
}
/**
@@ -138,6 +146,17 @@ public class TreeGrid<T> extends Grid<T> {
public T getExpandedItem() {
return expandedItem;
}
+
+ /**
+ * Returns whether this event was triggered by user interaction, on the
+ * client side, or programmatically, on the server side.
+ *
+ * @return {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
}
/**
@@ -153,6 +172,8 @@ public class TreeGrid<T> extends Grid<T> {
private final T collapsedItem;
+ private final boolean userOriginated;
+
/**
* Construct a collapse event.
*
@@ -160,10 +181,15 @@ public class TreeGrid<T> extends Grid<T> {
* the tree grid this event originated from
* @param collapsedItem
* the item that was collapsed
+ * @param userOriginated
+ * whether the collapse was triggered by a user interaction
+ * or the server
*/
- public CollapseEvent(TreeGrid<T> source, T collapsedItem) {
+ public CollapseEvent(TreeGrid<T> source, T collapsedItem,
+ boolean userOriginated) {
super(source);
this.collapsedItem = collapsedItem;
+ this.userOriginated = userOriginated;
}
/**
@@ -174,6 +200,17 @@ public class TreeGrid<T> extends Grid<T> {
public T getCollapsedItem() {
return collapsedItem;
}
+
+ /**
+ * Returns whether this event was triggered by user interaction, on the
+ * client side, or programmatically, on the server side.
+ *
+ * @return {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
}
public TreeGrid() {
@@ -182,16 +219,18 @@ public class TreeGrid<T> extends Grid<T> {
registerRpc(new NodeCollapseRpc() {
@Override
public void setNodeCollapsed(String rowKey, int rowIndex,
- boolean collapse) {
+ boolean collapse, boolean userOriginated) {
if (collapse) {
- if (getDataCommunicator().doCollapse(rowKey, rowIndex)) {
+ if (getDataCommunicator().doCollapse(rowKey, rowIndex)
+ && userOriginated) {
fireCollapseEvent(getDataCommunicator().getKeyMapper()
- .get(rowKey));
+ .get(rowKey), true);
}
} else {
- if (getDataCommunicator().doExpand(rowKey, rowIndex)) {
+ if (getDataCommunicator().doExpand(rowKey, rowIndex)
+ && userOriginated) {
fireExpandEvent(getDataCommunicator().getKeyMapper()
- .get(rowKey));
+ .get(rowKey), true);
}
}
}
@@ -400,6 +439,37 @@ public class TreeGrid<T> extends Grid<T> {
getDataCommunicator().setItemCollapseAllowedProvider(provider);
}
+ /**
+ * Expands the given item.
+ * <p>
+ * If the item is currently expanded, does nothing. If the item does not
+ * have any children, does nothing.
+ *
+ * @param item
+ * the item to expand
+ */
+ public void expand(T item) {
+ getDataCommunicator().setPendingExpand(item).ifPresent(key -> {
+ getRpcProxy(TreeGridClientRpc.class).setExpanded(key);
+ fireExpandEvent(item, false);
+ });
+ }
+
+ /**
+ * Collapses the given item.
+ * <p>
+ * If the item is already collapsed, does nothing.
+ *
+ * @param item
+ * the item to collapse
+ */
+ public void collapse(T item) {
+ getDataCommunicator().collapseItem(item).ifPresent(key -> {
+ getRpcProxy(TreeGridClientRpc.class).setCollapsed(key);
+ fireCollapseEvent(item, false);
+ });
+ }
+
@Override
protected TreeGridState getState() {
return (TreeGridState) super.getState();
@@ -527,9 +597,12 @@ public class TreeGrid<T> extends Grid<T> {
*
* @param item
* the item that was expanded
+ * @param userOriginated
+ * whether the expand was triggered by a user interaction or the
+ * server
*/
- private void fireExpandEvent(T item) {
- fireEvent(new ExpandEvent<>(this, item));
+ private void fireExpandEvent(T item, boolean userOriginated) {
+ fireEvent(new ExpandEvent<>(this, item, userOriginated));
}
/**
@@ -537,8 +610,11 @@ public class TreeGrid<T> extends Grid<T> {
*
* @param item
* the item that was collapsed
+ * @param userOriginated
+ * whether the collapse was triggered by a user interaction or
+ * the server
*/
- private void fireCollapseEvent(T item) {
- fireEvent(new CollapseEvent<>(this, item));
+ private void fireCollapseEvent(T item, boolean userOriginated) {
+ fireEvent(new CollapseEvent<>(this, item, userOriginated));
}
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/treegrid/NodeCollapseRpc.java b/shared/src/main/java/com/vaadin/shared/ui/treegrid/NodeCollapseRpc.java
index 1b16203648..f0748a9b88 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/treegrid/NodeCollapseRpc.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/treegrid/NodeCollapseRpc.java
@@ -36,6 +36,10 @@ public interface NodeCollapseRpc extends ServerRpc {
* index where the row is in grid (all rows)
* @param collapse
* {@code true} to collapse, {@code false} to expand
+ * @param userOriginated
+ * {@code true} if this RPC was triggered by a user interaction,
+ * {@code false} otherwise
*/
- void setNodeCollapsed(String rowKey, int rowIndex, boolean collapse);
+ void setNodeCollapsed(String rowKey, int rowIndex, boolean collapse,
+ boolean userOriginated);
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/treegrid/TreeGridClientRpc.java b/shared/src/main/java/com/vaadin/shared/ui/treegrid/TreeGridClientRpc.java
new file mode 100644
index 0000000000..a07a2d4885
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/treegrid/TreeGridClientRpc.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.treegrid;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * Server-to-client RPC interface for the TreeGrid component.
+ *
+ * @since 8.1
+ * @author Vaadin Ltd
+ */
+public interface TreeGridClientRpc extends ClientRpc {
+
+ /**
+ * Inform the client that an item with the given key has been expanded by
+ * the server.
+ *
+ * @param key
+ * the communication key of the expanded item
+ */
+ public void setExpanded(String key);
+
+ /**
+ * Inform the client that an item with the given key has been collapsed by
+ * the server.
+ *
+ * @param key
+ * the communication key of the collapsed item
+ */
+ public void setCollapsed(String key);
+}
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 ae011355c9..a8ce0a7489 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
@@ -63,6 +63,8 @@ public class TreeGridBasicFeatures extends AbstractComponentTest<TreeGrid> {
createDataProviderSelect();
createHierarchyColumnSelect();
createCollapseAllowedSelect();
+ createExpandMenu();
+ createCollapseMenu();
createListenerMenu();
}
@@ -146,13 +148,45 @@ public class TreeGridBasicFeatures extends AbstractComponentTest<TreeGrid> {
}
@SuppressWarnings("unchecked")
+ private void createExpandMenu() {
+ createCategory("Server-side expand", CATEGORY_FEATURES);
+ createClickAction("Expand 0 | 0", "Server-side expand",
+ (treeGrid, value, data) -> treeGrid.expand(value),
+ new HierarchicalTestBean(null, 0, 0));
+ createClickAction("Expand 1 | 1", "Server-side expand",
+ (treeGrid, value, data) -> treeGrid.expand(value),
+ new HierarchicalTestBean("/0/0", 1, 1));
+ createClickAction("Expand 2 | 1", "Server-side expand",
+ (treeGrid, value, data) -> treeGrid.expand(value),
+ new HierarchicalTestBean("/0/0/1/1", 2, 1));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void createCollapseMenu() {
+ createCategory("Server-side collapse", CATEGORY_FEATURES);
+ createClickAction("Collapse 0 | 0", "Server-side collapse",
+ (treeGrid, value, data) -> treeGrid.collapse(value),
+ new HierarchicalTestBean(null, 0, 0));
+ createClickAction("Collapse 1 | 1", "Server-side collapse",
+ (treeGrid, value, data) -> treeGrid.collapse(value),
+ new HierarchicalTestBean("/0/0", 1, 1));
+ createClickAction("Collapse 2 | 1", "Server-side collapse",
+ (treeGrid, value, data) -> treeGrid.collapse(value),
+ new HierarchicalTestBean("/0/0/1/1", 2, 1));
+ }
+
+ @SuppressWarnings("unchecked")
private void createListenerMenu() {
createListenerAction("Collapse listener", "State",
treeGrid -> treeGrid.addCollapseListener(event -> log(
- "Item collapsed: " + event.getCollapsedItem())));
+ "Item collapsed (user originated: "
+ + event.isUserOriginated() + "): "
+ + event.getCollapsedItem())));
createListenerAction("Expand listener", "State",
treeGrid -> treeGrid.addExpandListener(event -> log(
- "Item expanded: " + event.getExpandedItem())));
+ "Item expanded (user originated: "
+ + event.isUserOriginated() + "): "
+ + event.getExpandedItem())));
}
static class HierarchicalTestBean {
diff --git a/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTree.java b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTree.java
new file mode 100644
index 0000000000..7cfb926849
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTree.java
@@ -0,0 +1,57 @@
+package com.vaadin.tests.components.treegrid;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.HierarchyData;
+import com.vaadin.data.provider.InMemoryHierarchicalDataProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.TreeGrid;
+
+@Theme("valo")
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class TreeGridHugeTree
+ extends AbstractTestUI {
+
+ private TreeGrid<String> treeGrid;
+ private InMemoryHierarchicalDataProvider<String> inMemoryDataProvider;
+
+ private void initializeDataProvider() {
+ HierarchyData<String> data = new HierarchyData<>();
+ for (int i = 0; i < 3; i++) {
+ String granddad = "Granddad " + i;
+ data.addItem(null, granddad);
+ for (int j = 0; j < 3; j++) {
+ String dad = "Dad " + i + "/" + j;
+ data.addItem(granddad, dad);
+ for (int k = 0; k < 300; k++) {
+ String son = "Son " + i + "/" + j + "/" + k;
+ data.addItem(dad, son);
+ }
+ }
+ }
+ inMemoryDataProvider = new InMemoryHierarchicalDataProvider<>(data);
+ }
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ initializeDataProvider();
+ treeGrid = new TreeGrid<>();
+ treeGrid.setDataProvider(inMemoryDataProvider);
+ treeGrid.setSizeFull();
+ treeGrid.addColumn(String::toString).setCaption("String")
+ .setId("string");
+ treeGrid.addColumn((i) -> "--").setCaption("Nothing");
+ treeGrid.setHierarchyColumn("string");
+ treeGrid.setId("testComponent");
+
+ Button expand = new Button("Expand Granddad 1");
+ expand.addClickListener(event -> treeGrid.expand("Granddad 1"));
+ Button collapse = new Button("Collapse Granddad 1");
+ collapse.addClickListener(event -> treeGrid.collapse("Granddad 1"));
+
+ addComponents(treeGrid, expand, collapse);
+ }
+
+} \ No newline at end of file
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 7fb7ddf3f3..dd4546c6c0 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
@@ -1,12 +1,13 @@
package com.vaadin.tests.components.treegrid;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import java.util.Arrays;
import java.util.Collection;
-import java.util.List;
-import com.vaadin.testbench.parallel.Browser;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized.Parameters;
@@ -18,10 +19,6 @@ import com.vaadin.testbench.elements.TreeGridElement;
import com.vaadin.tests.tb3.MultiBrowserTest;
import com.vaadin.tests.tb3.ParameterizedTB3Runner;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
@RunWith(ParameterizedTB3Runner.class)
public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
@@ -41,28 +38,82 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
@Before
public void before() {
setDebug(true);
- openTestURL("theme=valo");
+ openTestURL();
grid = $(TreeGridElement.class).first();
}
@Test
- @Ignore // currently no implementation exists for toggling from the server
- // side
public void toggle_collapse_server_side() {
assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
- selectMenuPath("Component", "Features", "Toggle expand", "0 | 0");
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
+
+ // expanding already expanded item should have no effect
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
assertEquals(6, grid.getRowCount());
assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
- selectMenuPath("Component", "Features", "Toggle expand", "0 | 0");
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0");
+ assertEquals(3, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+
+ // collapsing the same item twice should have no effect
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0");
+ assertEquals(3, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 1 | 1");
+ // 1 | 1 not yet visible, shouldn't immediately expand anything
assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
+ // 1 | 1 becomes visible and is also expanded
+ assertEquals(9, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "2 | 0", "2 | 1",
+ "2 | 2", "1 | 2" });
+
// collapsing a leaf should have no effect
- selectMenuPath("Component", "Features", "Toggle expand", "1 | 0");
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 2 | 1");
+ assertEquals(9, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "2 | 0", "2 | 1",
+ "2 | 2", "1 | 2" });
+
+ // collapsing 0 | 0 should collapse the expanded 1 | 1
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0");
assertEquals(3, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+
+ // 1 | 1 should not be expanded this time
+ 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();
+ }
+
+ @Test
+ public void pending_expands_cleared_when_data_provider_set() {
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 1 | 1");
+ selectMenuPath("Component", "Features", "Set data provider",
+ "LazyHierarchicalDataProvider");
+ grid.expandWithClick(0);
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
}
@Test
@@ -105,20 +156,21 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
// Should expand "1 | 1" without moving focus
new Actions(getDriver()).sendKeys(Keys.RIGHT).perform();
assertEquals(9, grid.getRowCount());
- assertCellTexts(2, 0, new String[] { "1 | 1", "2 | 0", "2 | 1", "2 | 2", "1 | 2"});
+ assertCellTexts(2, 0,
+ new String[] { "1 | 1", "2 | 0", "2 | 1", "2 | 2", "1 | 2" });
assertTrue(grid.getRow(2).hasClassName("v-grid-rowmode-row-focused"));
// Should collapse "1 | 1"
new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
assertEquals(6, grid.getRowCount());
- assertCellTexts(2, 0, new String[] { "1 | 1", "1 | 2", "0 | 1"});
+ assertCellTexts(2, 0, new String[] { "1 | 1", "1 | 2", "0 | 1" });
assertTrue(grid.getRow(2).hasClassName("v-grid-rowmode-row-focused"));
-
// Should navigate to "0 | 0"
new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
assertEquals(6, grid.getRowCount());
- assertCellTexts(0, 0, new String[] { "0 | 0", "1 | 0", "1 | 1", "1 | 2" , "0 | 1" });
+ assertCellTexts(0, 0,
+ new String[] { "0 | 0", "1 | 0", "1 | 1", "1 | 2", "0 | 1" });
assertTrue(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
// Should collapse "0 | 0"
@@ -171,18 +223,40 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
selectMenuPath("Component", "State", "Expand listener");
selectMenuPath("Component", "State", "Collapse listener");
- assertFalse(logContainsText("Item expanded: 0 | 0"));
- assertFalse(logContainsText("Item collapsed: 0 | 0"));
+ assertFalse(logContainsText(
+ "Item expanded (user originated: true): 0 | 0"));
+ assertFalse(logContainsText(
+ "Item collapsed (user originated: true): 0 | 0"));
grid.expandWithClick(0);
- assertTrue(logContainsText("Item expanded: 0 | 0"));
- assertFalse(logContainsText("Item collapsed: 0 | 0"));
+ assertTrue(logContainsText(
+ "Item expanded (user originated: true): 0 | 0"));
+ assertFalse(logContainsText(
+ "Item collapsed (user originated: true): 0 | 0"));
grid.collapseWithClick(0);
- assertTrue(logContainsText("Item expanded: 0 | 0"));
- assertTrue(logContainsText("Item collapsed: 0 | 0"));
+ assertTrue(logContainsText(
+ "Item expanded (user originated: true): 0 | 0"));
+ assertTrue(logContainsText(
+ "Item collapsed (user originated: true): 0 | 0"));
+
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
+
+ assertTrue(logContainsText(
+ "Item expanded (user originated: false): 0 | 0"));
+ assertFalse(logContainsText(
+ "Item collapsed (user originated: false): 0 | 0"));
+
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0");
+
+ assertTrue(logContainsText(
+ "Item expanded (user originated: false): 0 | 0"));
+ assertTrue(logContainsText(
+ "Item collapsed (user originated: false): 0 | 0"));
selectMenuPath("Component", "State", "Expand listener");
selectMenuPath("Component", "State", "Collapse listener");
@@ -190,8 +264,10 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
grid.expandWithClick(1);
grid.collapseWithClick(1);
- assertFalse(logContainsText("Item expanded: 0 | 1"));
- assertFalse(logContainsText("Item collapsed: 0 | 1"));
+ assertFalse(logContainsText(
+ "Item expanded (user originated: true): 0 | 1"));
+ assertFalse(logContainsText(
+ "Item collapsed (user originated: true): 0 | 1"));
}
private void assertCellTexts(int startRowIndex, int cellIndex,
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeTest.java
new file mode 100644
index 0000000000..1d5d9b309d
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeTest.java
@@ -0,0 +1,60 @@
+package com.vaadin.tests.components.treegrid;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.TreeGridElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class TreeGridHugeTreeTest extends SingleBrowserTest {
+
+ private TreeGridElement grid;
+
+ @Test
+ public void toggle_expand_when_row_out_of_cache() {
+ openTestURL();
+
+ grid = $(TreeGridElement.class).first();
+ ButtonElement expandSecondRowButton = $(ButtonElement.class).get(0);
+ ButtonElement collapseSecondRowButton = $(ButtonElement.class).get(1);
+
+ grid.expandWithClick(2);
+ grid.expandWithClick(3);
+ grid.scrollToRow(300);
+ grid.waitForVaadin();
+
+ expandSecondRowButton.click();
+
+ grid.scrollToRow(0);
+ assertCellTexts(0, 0, new String[] { "Granddad 0", "Granddad 1",
+ "Dad 1/0", "Dad 1/1", "Dad 1/2", "Granddad 2", "Dad 2/0" });
+
+ grid.scrollToRow(300);
+ grid.waitForVaadin();
+ collapseSecondRowButton.click();
+ grid.scrollToRow(0);
+ grid.waitForVaadin();
+ assertCellTexts(0, 0, new String[] { "Granddad 0", "Granddad 1",
+ "Granddad 2", "Dad 2/0" });
+
+ grid.scrollToRow(300);
+ grid.waitForVaadin();
+ expandSecondRowButton.click();
+ collapseSecondRowButton.click();
+ grid.scrollToRow(0);
+ grid.waitForVaadin();
+ assertCellTexts(0, 0, new String[] { "Granddad 0", "Granddad 1",
+ "Granddad 2", "Dad 2/0" });
+ }
+
+ private void assertCellTexts(int startRowIndex, int cellIndex,
+ String[] cellTexts) {
+ int index = startRowIndex;
+ for (String cellText : cellTexts) {
+ Assert.assertEquals(cellText,
+ grid.getRow(index).getCell(cellIndex).getText());
+ index++;
+ }
+ }
+}